os.path.commonprefix () e os.path.relpath () são seus amigos:
>>> 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
'/'
Assim, você pode testar se o prefixo comum é um dos caminhos, ou seja, se um dos caminhos é um ancestral comum:
paths = […, …, …]
common_prefix = os.path.commonprefix(list_of_paths)
if common_prefix in paths:
…
Você pode encontrar os caminhos relativos:
relative_paths = [os.path.relpath(path, common_prefix) for path in paths]
Você pode até manipular mais de dois caminhos, com esse método, e testar se todos os caminhos estão todos abaixo de um deles.
PS : dependendo da aparência de seus caminhos, primeiro você deve executar alguma normalização (isso é útil em situações em que não se sabe se elas sempre terminam com '/' ou não, ou se alguns deles são relativos). Funções relevantes incluem os.path.abspath () e os.path.normpath () .
PPS : como Peter Briggs mencionou nos comentários, a abordagem simples descrita acima pode falhar:
>>> os.path.commonprefix(['/usr/var', '/usr/var2/log'])
'/usr/var'
mesmo que não/usr/var
seja um prefixo comum dos caminhos. Forçar todos os caminhos a terminarem com '/' antes de chamar resolve esse problema (específico).commonprefix()
PPPS : como o bluenote10 mencionou, a adição de uma barra não resolve o problema geral. Aqui está sua pergunta seguinte: Como contornar a falácia do os.path.commonprefix do Python?
PPPPS : a partir do Python 3.4, temos o pathlib , um módulo que fornece um ambiente de manipulação de caminhos mais saudável . Eu acho que o prefixo comum de um conjunto de caminhos pode ser obtido obtendo todos os prefixos de cada caminho (com PurePath.parents()
), fazendo a interseção de todos esses conjuntos pai e selecionando o prefixo comum mais longo.
PPPPPS : Python 3.5 introduziu uma solução adequada para esta pergunta :,os.path.commonpath()
que retorna um caminho válido.
commonprefix
, por exemplo, o prefixo comum para/usr/var/log
e/usr/var2/log
é retornado como/usr/var
- o que provavelmente não é o que você esperaria. (Também é possível para que ele retorne caminhos que não são diretórios válidos.)['/usr/var1/log/', '/usr/var2/log/']
?os.path.relpath
:Portanto, se o caminho relativo começar com
'..'
- isso significa que o segundo caminho não é descendente do primeiro caminho.No Python3 você pode usar
PurePath.relative_to
:fonte
os.pardir
é mais robusto do que verificar..
(concordado, não existem muitas outras convenções).os.relpath
mais poderoso, pois lida com..
ePurePath.relative_to()
não? Estou esquecendo de algo?Outra opção é
fonte
os.pardir
no entanto, é possível verificar a presença de dois caminhos relativos resultantes).Uma descrição da sugestão de jme, usando pathlib, em Python 3.
fonte
dir1.relative_to(dir2)
, dará PosixPath ('.') Se eles forem iguais. Quando você usaif dir2 in dir1.parents
, ele exclui o caso de identidade. Se alguém estiver comparando Paths e quiser executarrelative_to()
se for compatível com o caminho, uma solução melhor pode serif dir2 in (dir1 / 'x').parents
ouif dir2 in dir1.parents or dir2 == dir1
. Todos os casos de compatibilidade de caminho são abordados.Pure Python2 sem dep:
fonte
cwd
epath
é o mesmo. deve verificar primeiro se esses dois são iguais e retornar""
ou"."
Edit: Veja a resposta do jme para a melhor maneira com o Python3.
Usando pathlib, você tem a seguinte solução:
Digamos que queremos verificar se
son
é um descendente deparent
e ambos sãoPath
objetos. Podemos obter uma lista das partes no caminholist(parent.parts)
. Então, apenas verificamos que o início do filho é igual à lista de segmentos do pai.Se você deseja obter a parte restante, basta fazer
É uma string, mas é claro que você pode usá-la como construtora de outro objeto Path.
fonte
parent in son.parents
, e se for, obtendo o restanteson.relative_to(parent)
.