O atributo do módulo __file__ é absoluto ou relativo?

107

Estou tendo problemas para entender __file__. Pelo que entendi, __file__retorna o caminho absoluto a partir do qual o módulo foi carregado.

Estou tendo problemas para produzir isto: tenho um abc.pycom uma instrução print __file__, executando a partir de /d/projects/ python abc.pyretornos abc.py. correndo de /d/retornos projects/abc.py. Alguma razão para quê?

goh
fonte
10
Aqui está o que Guido tem a dizer sobre isso: mail.python.org/pipermail/python-dev/2010-February/097461.html
kindall
Relevante: stackoverflow.com/q/9271464/1959808
Ioannis Filippidis

Respostas:

98

Da documentação :

__file__é o nome do caminho do arquivo do qual o módulo foi carregado, se ele foi carregado de um arquivo. O __file__atributo não está presente para módulos C que estão estaticamente vinculados ao interpretador; para módulos de extensão carregados dinamicamente de uma biblioteca compartilhada, é o nome do caminho do arquivo da biblioteca compartilhada.

Do tópico da lista de e-mails vinculado por @kindall em um comentário para a pergunta:

Não tentei reproduzir este exemplo específico, mas o motivo é que não queremos ter que chamar getpwd () em cada importação, nem queremos ter algum tipo de variável em processo para armazenar em cache o diretório atual. (getpwd () é relativamente lento e às vezes pode falhar completamente, e tentar armazená-lo em cache apresenta um certo risco de dar errado.)

Em vez disso, o que fazemos é o código em site.py que percorre os elementos de sys.path e os transforma em caminhos absolutos. No entanto, esse código é executado antes de '' ser inserido na frente de sys.path, de forma que o valor inicial de sys.path seja ''.

Para o resto, considere sys.pathnão incluir ''.

Então, se você estiver fora da parte sys.pathque contém o módulo, você obterá um caminho absoluto . Se você estiver dentro da parte sys.pathque contém o módulo, obterá um caminho relativo .

Se você carregar um módulo no diretório atual, eo diretório atual não está em sys.path, você vai ter um caminho absoluto.

Se você carregar um módulo no diretório atual, e o diretório atual estiver dentro sys.path, você obterá um caminho relativo.

agf
fonte
isso significa que se houver um caminho de '' para o módulo, um caminho relativo seria usado, caso contrário, um caminho absoluto seria usado, já que o restante de sys.path é absoluto ..
goh
4
Se você carregar um módulo no diretório atual, eo diretório atual não está em sys.path, você vai ter um caminho absoluto. Se você carregar um módulo no diretório atual, e o diretório atual estiver dentro sys.path, você obterá um caminho relativo.
agf
Lembre-se, para este propósito, sys.pathnão inclui ''.
agf
entendi, mas @agf, se eu usar python /foo/abc.py em / home, suponho que a parte de sys.path que contém o módulo é / home / foo e meu diretório atual é / home /, por que imprimir arquivo me dá um caminho relativo?
goh
55

__file__é absoluto desde o Python 3.4 , exceto ao executar um script diretamente usando um caminho relativo:

Os __file__atributos do módulo (e valores relacionados) agora devem sempre conter caminhos absolutos por padrão, com a única exceção de __main__.__file__quando um script foi executado diretamente usando um caminho relativo. (Contribuição de Brett Cannon em bpo-18416 .)

Não tenho certeza se isso resolve links simbólicos embora.

Exemplo de passagem de um caminho relativo:

$ python script.py
anatoly techtonik
fonte
1
Obrigado. Este é um fato difícil de rastrear!
meawoppl
4
Isso não é verdade para Python 3.4.0 ( Python 3.4.0 (default, Apr 11 2014, 13:05:11) [GCC 4.8.2] on linux). E os links simbólicos não são resolvidos em meus testes.
Frozen Flame
@FrozenFlame, sinta-se à vontade para relatar para bugs.python.org se 3.4.1 não corrigir isso.
anatoly techtonik
2
É os.path.realpath(__file__)a maneira correta de resolver links simbólicos?
kevinarpe
3
@kevinarpe, stackoverflow.com/questions/3220755/…
anatoly techtonik
16

Exemplo simples tardio:

from os import path, getcwd, chdir

def print_my_path():
    print('cwd:     {}'.format(getcwd()))
    print('__file__:{}'.format(__file__))
    print('abspath: {}'.format(path.abspath(__file__)))

print_my_path()

chdir('..')

print_my_path()

Em Python-2. *, A segunda chamada determina incorretamente o com path.abspath(__file__)base no diretório atual:

cwd:     C:\codes\py
__file__:cwd_mayhem.py
abspath: C:\codes\py\cwd_mayhem.py
cwd:     C:\codes
__file__:cwd_mayhem.py
abspath: C:\codes\cwd_mayhem.py

Conforme observado por @techtonik, no Python 3.4+, isso funcionará bem, pois __file__retorna um caminho absoluto.

SimplyKnownAsG
fonte
... exceto para o __main__módulo, onde __file__ pode ser um caminho relativo.
0xC0000022L
5

Com a ajuda do e-mail do Guido fornecido por @kindall, podemos entender o processo de importação padrão como tentar encontrar o módulo em cada membro de sys.path, e arquivo como resultado desta consulta (mais detalhes em Módulos PyMOTW e Importações .). Portanto, se o módulo está localizado em um caminho absoluto, sys.patho resultado é absoluto, mas se ele está localizado em um caminho relativo, sys.patho resultado é relativo.

Agora, o site.pyarquivo de inicialização se encarrega de entregar apenas o caminho absoluto sys.path, exceto o inicial '', então se você não alterá-lo por outro meio que não seja definindo o PYTHONPATH (cujo caminho também é feito absoluto, antes de prefixar sys.path), você obterá sempre um absoluto caminho, mas quando o módulo é acessado através do diretório atual.

Agora, se você enganar o sys.path de uma maneira engraçada, poderá conseguir qualquer coisa.

Como exemplo, se você tem um módulo de exemplo foo.pyno /tmp/com o código:

import sys
print(sys.path)
print (__file__)

Se você entrar em / tmp, obterá:

>>> import foo
['', '/tmp', '/usr/lib/python3.3', ...]
./foo.py

Quando em /home/user, se você adicionar /tmpseu, PYTHONPATHvocê terá:

>>> import foo
['', '/tmp', '/usr/lib/python3.3', ...]
/tmp/foo.py

Mesmo se você adicionar ../../tmp, ele será normalizado e o resultado será o mesmo.

Mas se em vez de usar PYTHONPATHvocê usar diretamente algum caminho engraçado você obtém um resultado tão engraçado quanto a causa.

>>> import sys
>>> sys.path.append('../../tmp')
>>> import foo
['', '/usr/lib/python3.3', .... , '../../tmp']
../../tmp/foo.py

Guido explica no tópico citado acima, por que o python não tenta transformar todas as entradas em caminhos absolutos:

não queremos ter que chamar getpwd () em cada importação .... getpwd () é relativamente lento e às vezes pode falhar completamente,

Portanto, seu caminho é usado como está .

Marcz
fonte