Python: İki mutlak yolu karşılaştırarak göreceli yol alın


143

Diyelim ki iki mutlak yolum var. Yollardan biri tarafından atıfta bulunan konum diğerinin soyundan olup olmadığını kontrol etmek gerekir. Eğer doğruysa, soyundan atadan gelen göreli yolu bulmam gerekiyor. Bunu Python'da uygulamanın iyi bir yolu nedir? Yararlanabileceğim herhangi bir kütüphane?

Yanıtlar:


168

os.path.commonprefix () ve os.path.relpath () arkadaşlarınız:

>>> 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
'/'

Böylece, ortak önekin yollardan biri olup olmadığını, yani yollardan birinin ortak bir ata olup olmadığını test edebilirsiniz:

paths = […, …, …]
common_prefix = os.path.commonprefix(list_of_paths)
if common_prefix in paths:
    

Ardından göreli yolları bulabilirsiniz:

relative_paths = [os.path.relpath(path, common_prefix) for path in paths]

Hatta bu yöntemle ikiden fazla yolu işleyebilir ve tüm yolların bunlardan birinin altında olup olmadığını test edebilirsiniz.

Not : Yollarınızın nasıl göründüğüne bağlı olarak, önce bir miktar normalleştirme yapmak isteyebilirsiniz (bu, her zaman '/' ile bitip bitmeyeceklerini veya bazı yolların göreceli olup olmadığını bilmedikleri durumlarda yararlıdır). İlgili işlevler arasında os.path.abspath () ve os.path.normpath () yer alır .

PPS : Peter Briggs'in yorumlarda belirttiği gibi, yukarıda açıklanan basit yaklaşım başarısız olabilir:

>>> os.path.commonprefix(['/usr/var', '/usr/var2/log'])
'/usr/var'

olsa bile /usr/varolduğunu değil yollarının ortak önek. Aramadan önce tüm yolları '/' ile bitmeye zorlamak commonprefix()bu (özel) sorunu çözer.

PPPS : bluenote10'un belirttiği gibi, eğik çizgi eklemek genel sorunu çözmez. Takip eden sorusu: Python'un os.path.commonprefix'in yanılsamasını nasıl atlayabiliriz?

PPPPS : Python 3.4 ile başlayarak, daha akılcı bir yol manipülasyon ortamı sağlayan bir modül olan pathlib var . Bir yol kümesinin ortak önekinin, her bir yolun tüm öneklerini (ile PurePath.parents()) alarak, tüm bu üst kümelerin kesişimini alarak ve en uzun ortak öneki seçerek elde edilebileceğini tahmin ediyorum .

PPPPPS : Python 3.5 bu soruya os.path.commonpath()geçerli bir yol döndüren uygun bir çözüm sundu:


Tam olarak ihtiyacım olan şey. Hızlı cevabın için teşekkürler. Zaman kısıtlaması kaldırıldığında cevabınızı kabul edecektir.
tamakisquare

10
İle özen commonprefixiçin ortak bir önek örneğin gibi /usr/var/logve /usr/var2/loggeri dönerse /usr/varne beklediğiniz muhtemelen olmadığı -. (Geçerli dizinler olmayan yollar döndürmesi de mümkündür.)
Peter Briggs

@PeterBriggs: Teşekkürler, bu uyarı önemlidir. Bir PPS ekledim.
Eric O Lebigot

1
@EOL: Bir eğik çizgi ekleyerek sorunun nasıl çözüleceğini gerçekten göremiyorum :(. Ya ['/usr/var1/log/', '/usr/var2/log/']
sahipsek

1
@EOL: Bu sorun için cazip bir çözüm bulamadığım için bu alt konuyu ayrı bir soruda tartışmak uygun olabilir .
bluenote10

86

os.path.relpath:

Geçerli dizinden veya isteğe bağlı bir başlangıç ​​noktasından yola göreceli bir dosya yolu döndürür.

>>> from os.path import relpath
>>> relpath('/usr/var/log/', '/usr/var')
'log'
>>> relpath('/usr/var/log/', '/usr/var/sad/')
'../log'

Yani, göreli yol ile başlarsa '..'- ikinci yolun ilk yolun torunu olmadığı anlamına gelir.

Python3'te şunları kullanabilirsiniz 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'

2
Varlığını os.pardirkontrol etmek, kontrol etmekten daha sağlamdır ..(kabul edildi, ancak başka birçok sözleşme yoktur).
Eric O Lebigot

8
Yanlış mıyım yoksa os.relpathidare ettiği ..ve PurePath.relative_to()yapmadığı için daha mı güçlü ? Bir şey mi kaçırıyorum?
Ray Salemi

15

Başka bir seçenek

>>> print os.path.relpath('/usr/var/log/', '/usr/var')
log

Bu her zaman göreceli bir yol döndürür; bu, yollardan birinin diğerinin üzerinde olup olmadığını doğrudan göstermez (yine os.pardirde, olası iki sonuç bağıl yolun önünde olup olmadığını kontrol edebilir ).
Eric O Lebigot

8

Python 3'te pathlib kullanarak jme'nin önerisinin yazılması.

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'

Yani dir1.relative_to(dir2)PosixPath verecektir ( '') aynı olup olmadığını. Kullandığınızda if dir2 in dir1.parentskimlik durumu hariç tutulur. Birisi Yolları karşılaştırıyorsa ve relative_to()yol uyumluysa çalıştırmak istiyorsa, daha iyi bir çözüm olabilir if dir2 in (dir1 / 'x').parentsveya if dir2 in dir1.parents or dir2 == dir1. Daha sonra tüm yol uyumluluğu vakaları kapsanır.
ingyhere

3

Deplasmansız saf 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 "."

Bu iyi görünüyor, ama, tökezlediğimde , aynı cwdve ne zaman bir sorun var path. önce bu ikisinin aynı olup olmadığını kontrol etmeli ya ""da"."
Srđan Popić

1

Düzenleme: Python3 ile en iyi yolu için jme cevap bakın.

Pathlib'i kullanarak aşağıdaki çözümlere sahipsiniz:

Diyelim ki son, soyundan gelip gelmediğini parentve her ikisinin de Pathnesne olup olmadığını kontrol etmek istiyoruz . İle yoldaki parçaların bir listesini alabiliriz list(parent.parts). Sonra, oğlun başlangıcının ebeveynin segment listesine eşit olduğunu kontrol ediyoruz.

>>> lparent = list(parent.parts)
>>> lson = list(son.parts)
>>> if lson[:len(lparent)] == lparent:
>>> ... #parent is a parent of son :)

Kalan kısmı almak istiyorsanız,

>>> ''.join(lson[len(lparent):])

Bu bir dize, ancak elbette başka bir Path nesnesinin kurucusu olarak kullanabilirsiniz.


4
Bundan daha da kolay: basitçe parent in son.parentsve eğer öyleyse, geri kalanını almak son.relative_to(parent).
jme

@jme Cevabınız daha iyi, neden göndermiyorsunuz?
Jeremy Cochoy
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.