บอกว่าฉันมีสองเส้นทางสัมบูรณ์ ฉันต้องตรวจสอบว่าตำแหน่งที่อ้างถึงโดยหนึ่งในเส้นทางคือลูกหลานของอีก ถ้าเป็นจริงฉันต้องหาเส้นทางญาติของผู้สืบทอดจากบรรพบุรุษ เป็นวิธีที่ดีในการนำไปใช้ใน Python อะไร ห้องสมุดใดที่ฉันจะได้รับประโยชน์
บอกว่าฉันมีสองเส้นทางสัมบูรณ์ ฉันต้องตรวจสอบว่าตำแหน่งที่อ้างถึงโดยหนึ่งในเส้นทางคือลูกหลานของอีก ถ้าเป็นจริงฉันต้องหาเส้นทางญาติของผู้สืบทอดจากบรรพบุรุษ เป็นวิธีที่ดีในการนำไปใช้ใน Python อะไร ห้องสมุดใดที่ฉันจะได้รับประโยชน์
คำตอบ:
os.path.commonprefix ()และos.path.relpath ()เป็นเพื่อนของคุณ:
>>> print os.path.commonprefix(['/usr/var/log', '/usr/var/security'])
'/usr/var'
>>> print os.path.commonprefix(['/tmp', '/usr/var']) # No common prefix: the root is the common prefix
'/'
คุณสามารถทดสอบว่าคำนำหน้าทั่วไปเป็นหนึ่งในเส้นทางหรือไม่เช่นถ้าหนึ่งในเส้นทางนั้นเป็นบรรพบุรุษร่วมกัน:
paths = […, …, …]
common_prefix = os.path.commonprefix(list_of_paths)
if common_prefix in paths:
…
จากนั้นคุณสามารถค้นหาเส้นทางสัมพัทธ์:
relative_paths = [os.path.relpath(path, common_prefix) for path in paths]
คุณสามารถจัดการเส้นทางได้มากกว่าสองเส้นทางด้วยวิธีนี้และทดสอบว่าเส้นทางทั้งหมดอยู่ด้านล่างหนึ่งเส้นทางหรือไม่
ป.ล. : ขึ้นอยู่กับว่าเส้นทางของคุณมีลักษณะอย่างไรคุณอาจต้องการดำเนินการตามปกติก่อน (สิ่งนี้มีประโยชน์ในสถานการณ์ที่ไม่มีใครรู้ว่าพวกเขามักลงท้ายด้วย '/' หรือไม่หรือเส้นทางบางเส้นทางมีความสัมพันธ์กัน) ฟังก์ชั่นที่เกี่ยวข้อง ได้แก่os.path.abspath ()และos.path.normpath ()
PPS : ดังที่ Peter Briggs กล่าวไว้ในความคิดเห็นวิธีการง่าย ๆ ที่อธิบายข้างต้นอาจล้มเหลวได้:
>>> os.path.commonprefix(['/usr/var', '/usr/var2/log'])
'/usr/var'
แม้ว่า/usr/varจะไม่ใช่คำนำหน้าของเส้นทางทั่วไป การบังคับให้พา ธ ทั้งหมดจบด้วย '/' ก่อนที่จะโทรcommonprefix()แก้ไขปัญหานี้ (เฉพาะ)
PPPS : ตามที่ bluenote10 กล่าวไว้การเพิ่มเครื่องหมายสแลชไม่สามารถแก้ปัญหาทั่วไปได้ นี่คือคำถามติดตามของเขา: จะหลีกเลี่ยงการเข้าใจผิดของ os.path.commonprefix ของ Python ได้อย่างไร
PPPPS : เริ่มต้นด้วย Python 3.4 เรามีpathlibโมดูลที่จัดเตรียมสภาพแวดล้อมการจัดการพา ธ saner ฉันเดาว่าคำนำหน้าทั่วไปของชุดของเส้นทางสามารถรับได้โดยนำหน้าทั้งหมดของแต่ละเส้นทาง (ด้วยPurePath.parents()), การตัดกันของชุดผู้ปกครองเหล่านี้ทั้งหมดและเลือกคำนำหน้าทั่วไปที่ยาวที่สุด
PPPPPS : Python 3.5 แนะนำวิธีแก้ปัญหาที่เหมาะสมสำหรับคำถามนี้: os.path.commonpath()ซึ่งส่งคืนพา ธ ที่ถูกต้อง
commonprefixเช่นเช่นคำนำหน้าทั่วไปสำหรับ/usr/var/logและ/usr/var2/logส่งคืนเป็น/usr/var- ซึ่งอาจไม่ใช่สิ่งที่คุณคาดหวัง (อาจเป็นไปได้ที่จะส่งคืนพา ธ ที่ไม่ใช่ไดเรกทอรีที่ถูกต้อง)
['/usr/var1/log/', '/usr/var2/log/']?
ส่งคืนพา ธ ไฟล์แบบสัมพัทธ์ไปยังพา ธ จากไดเรกทอรีปัจจุบันหรือจากจุดเริ่มต้นที่เป็นทางเลือก
>>> from os.path import relpath
>>> relpath('/usr/var/log/', '/usr/var')
'log'
>>> relpath('/usr/var/log/', '/usr/var/sad/')
'../log'
ดังนั้นหากเส้นทางสัมพัทธ์เริ่มต้นด้วย'..'- หมายความว่าเส้นทางที่สองไม่ได้เป็นลูกหลานของเส้นทางแรก
ใน Python3 คุณสามารถใช้PurePath.relative_to:
Python 3.5.1 (default, Jan 22 2016, 08:54:32)
>>> from pathlib import Path
>>> Path('/usr/var/log').relative_to('/usr/var/log/')
PosixPath('.')
>>> Path('/usr/var/log').relative_to('/usr/var/')
PosixPath('log')
>>> Path('/usr/var/log').relative_to('/etc/')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/Cellar/python3/3.5.1/Frameworks/Python.framework/Versions/3.5/lib/python3.5/pathlib.py", line 851, in relative_to
.format(str(self), str(formatted)))
ValueError: '/usr/var/log' does not start with '/etc'
os.pardirมีประสิทธิภาพมากขึ้นกว่าการตรวจสอบ..(ตกลงมีไม่อนุสัญญาอื่น ๆ อีกมากมาย)
os.relpathมีพลังมากกว่านี้เพราะมันใช้งานได้..และPurePath.relative_to()ไม่? ฉันพลาดอะไรไปรึเปล่า?
อีกทางเลือกหนึ่งคือ
>>> print os.path.relpath('/usr/var/log/', '/usr/var')
log
os.pardirหน้าสองเส้นทางที่เป็นไปได้ที่สัมพันธ์กันหรือไม่)
บทความแนะนำของ jme โดยใช้ pathlib ใน Python 3
from pathlib import Path
parent = Path(r'/a/b')
son = Path(r'/a/b/c/d')
if parent in son.parents or parent==son:
print(son.relative_to(parent)) # returns Path object equivalent to 'c/d'
dir1.relative_to(dir2)จะให้ PosixPath ('.') หากพวกเขาเหมือนกัน เมื่อคุณใช้if dir2 in dir1.parentsแล้วมันจะไม่รวมกรณีตัวตน หากมีใครเปรียบเทียบเส้นทางและต้องการที่จะทำงานrelative_to()หากพวกเขาเป็นเส้นทางได้ทางออกที่ดีกว่าอาจจะเป็นหรือif dir2 in (dir1 / 'x').parents if dir2 in dir1.parents or dir2 == dir1จากนั้นทุกกรณีของความเข้ากันได้ของเส้นทางจะได้รับการคุ้มครอง
Pure Python2 ที่ไม่มีค่าใช้จ่าย:
def relpath(cwd, path):
"""Create a relative path for path from cwd, if possible"""
if sys.platform == "win32":
cwd = cwd.lower()
path = path.lower()
_cwd = os.path.abspath(cwd).split(os.path.sep)
_path = os.path.abspath(path).split(os.path.sep)
eq_until_pos = None
for i in xrange(min(len(_cwd), len(_path))):
if _cwd[i] == _path[i]:
eq_until_pos = i
else:
break
if eq_until_pos is None:
return path
newpath = [".." for i in xrange(len(_cwd[eq_until_pos+1:]))]
newpath.extend(_path[eq_until_pos+1:])
return os.path.join(*newpath) if newpath else "."
cwdและpathเป็นเหมือนกัน ควรตรวจสอบก่อนว่าทั้งสองอย่างนั้นเหมือนกัน""หรือไม่และกลับมาหรือ"."
แก้ไข:ดูคำตอบของ jme สำหรับวิธีที่ดีที่สุดด้วย Python3
การใช้ pathlib คุณมีวิธีแก้ไขปัญหาต่อไปนี้:
สมมติว่าเราต้องการตรวจสอบว่าsonเป็นผู้สืบทอดparentและทั้งสองเป็นPathวัตถุ เราได้รับรายการของที่ชิ้นส่วนlist(parent.parts)ในเส้นทางที่มี จากนั้นเราเพียงตรวจสอบว่าการเริ่มต้นของลูกชายเท่ากับรายชื่อของกลุ่มของผู้ปกครอง
>>> lparent = list(parent.parts)
>>> lson = list(son.parts)
>>> if lson[:len(lparent)] == lparent:
>>> ... #parent is a parent of son :)
หากคุณต้องการได้ส่วนที่เหลือคุณสามารถทำได้
>>> ''.join(lson[len(lparent):])
มันเป็นสตริง แต่แน่นอนคุณสามารถใช้มันเป็นตัวสร้างของวัตถุเส้นทางอื่น
parent in son.parents son.relative_to(parent)