O que a partir de __future__ import absolute_import realmente faz?

163

Tenho respondido uma pergunta sobre as importações absolutos em Python, que eu pensei que eu entendi com base na leitura do changelog Python 2.5 e acompanhando PEP . No entanto, ao instalar o Python 2.5 e tentar criar um exemplo de uso adequado from __future__ import absolute_import, percebo que as coisas não são tão claras.

Diretamente do changelog acima, esta declaração resumiu com precisão minha compreensão da mudança absoluta de importação:

Digamos que você tenha um diretório de pacotes como este:

pkg/
pkg/__init__.py
pkg/main.py
pkg/string.py

Isso define um pacote chamado pkgcontendo os sub pkg.main- pkg.stringmódulos e .

Considere o código no módulo main.py. O que acontece se a instrução for executada import string? No Python 2.4 e versões anteriores, ele procurará primeiro no diretório do pacote uma importação relativa, localiza pkg / string.py, importa o conteúdo desse arquivo como pkg.stringmódulo e esse módulo é vinculado ao nome "string"no pkg.mainespaço de nome do módulo.

Então, eu criei essa estrutura de diretório exata:

$ ls -R
.:
pkg/

./pkg:
__init__.py  main.py  string.py

__init__.pye string.pyestão vazios. main.pycontém o seguinte código:

import string
print string.ascii_uppercase

Como esperado, executar isso com o Python 2.5 falha com um AttributeError:

$ python2.5 pkg/main.py
Traceback (most recent call last):
  File "pkg/main.py", line 2, in <module>
    print string.ascii_uppercase
AttributeError: 'module' object has no attribute 'ascii_uppercase'

No entanto, mais adiante no changelog 2.5, encontramos o seguinte (ênfase adicionada):

No Python 2.5, você pode mudar importo comportamento para importações absolutas usando uma from __future__ import absolute_importdiretiva. Esse comportamento de importação absoluta se tornará o padrão em uma versão futura (provavelmente Python 2.7). Uma vez que as importações absolutas são o padrão, import stringsempre encontrará a versão da biblioteca padrão.

Criei assim pkg/main2.py, idêntico a, main.pymas com a futura diretiva de importação adicional. Agora, fica assim:

from __future__ import absolute_import
import string
print string.ascii_uppercase

Executar isso com o Python 2.5, no entanto ... falha com um AttributeError:

$ python2.5 pkg/main2.py
Traceback (most recent call last):
  File "pkg/main2.py", line 3, in <module>
    print string.ascii_uppercase
AttributeError: 'module' object has no attribute 'ascii_uppercase'

Este bastante categoricamente contradiz a afirmação de que import stringirá sempre encontrar a versão std-lib com as importações absolutos habilitado. Além do mais, apesar do aviso de que importações absolutas estão agendadas para se tornar o comportamento "novo padrão", encontrei o mesmo problema usando o Python 2.7, com ou sem a __future__diretiva:

$ python2.7 pkg/main.py
Traceback (most recent call last):
  File "pkg/main.py", line 2, in <module>
    print string.ascii_uppercase
AttributeError: 'module' object has no attribute 'ascii_uppercase'

$ python2.7 pkg/main2.py
Traceback (most recent call last):
  File "pkg/main2.py", line 3, in <module>
    print string.ascii_uppercase
AttributeError: 'module' object has no attribute 'ascii_uppercase'

bem como Python 3.5, com ou sem (assumindo que a printinstrução foi alterada nos dois arquivos):

$ python3.5 pkg/main.py
Traceback (most recent call last):
  File "pkg/main.py", line 2, in <module>
    print(string.ascii_uppercase)
AttributeError: module 'string' has no attribute 'ascii_uppercase'

$ python3.5 pkg/main2.py
Traceback (most recent call last):
  File "pkg/main2.py", line 3, in <module>
    print(string.ascii_uppercase)
AttributeError: module 'string' has no attribute 'ascii_uppercase'

Eu testei outras variações disso. Em vez de string.py, eu criei um módulo vazio - um diretório chamado stringcontendo apenas um vazio __init__.py- e em vez de emitir as importações a partir main.py, eu tenho cd'd para pkge executar as importações diretamente do REPL. Nenhuma dessas variações (nem uma combinação delas) alterou os resultados acima. Não consigo conciliar isso com o que li sobre a __future__diretiva e as importações absolutas.

Parece-me que isso é facilmente explicável pelo seguinte (isso é dos documentos do Python 2, mas essa declaração permanece inalterada nos mesmos documentos do Python 3):

sys.path

(...)

Conforme inicializado na inicialização do programa, o primeiro item desta lista path[0], é o diretório que contém o script usado para chamar o interpretador Python. Se o diretório do script não estiver disponível (por exemplo, se o intérprete for chamado interativamente ou se o script for lido a partir da entrada padrão), path[0]será a string vazia, que instrui o Python a pesquisar os módulos no diretório atual primeiro.

Então o que estou perdendo? Por que a __future__declaração aparentemente não faz o que diz e qual é a resolução dessa contradição entre essas duas seções da documentação, bem como entre o comportamento descrito e o real?

Alquimista de dois bits
fonte

Respostas:

104

O changelog é redigido de forma desleixada. from __future__ import absolute_importnão se importa se algo faz parte da biblioteca padrão e import stringnem sempre fornece o módulo da biblioteca padrão com importações absolutas.

from __future__ import absolute_importsignifica que, se você import string, o Python sempre procurará um stringmódulo de nível superior , em vez de current_package.string. No entanto, isso não afeta a lógica que o Python usa para decidir qual arquivo é o stringmódulo. Quando você faz

python pkg/script.py

pkg/script.pynão parece parte de um pacote para o Python. Seguindo os procedimentos normais, o pkgdiretório é adicionado ao caminho e todos os .pyarquivos no pkgdiretório se parecem com módulos de nível superior. import stringencontra pkg/string.pynão porque está fazendo uma importação relativa, mas porque pkg/string.pyparece ser o módulo de nível superior string. O fato de esse não ser o stringmódulo da biblioteca padrão não aparece.

Para executar o arquivo como parte do pkgpacote, você pode fazer

python -m pkg.script

Nesse caso, o pkgdiretório não será adicionado ao caminho. No entanto, o diretório atual será adicionado ao caminho.

Você também pode adicionar alguns clichês para pkg/script.pyfazer o Python tratá-lo como parte do pkgpacote, mesmo quando executado como um arquivo:

if __name__ == '__main__' and __package__ is None:
    __package__ = 'pkg'

No entanto, isso não afetará sys.path. Você precisará de um tratamento adicional para remover o pkgdiretório do caminho, e se pkgo diretório pai não estiver no caminho, você também precisará colocá-lo no caminho.

user2357112 suporta Monica
fonte
2
OK, quero dizer, eu entendi. Esse é exatamente o comportamento que minha postagem está documentando. Diante disso, porém, duas perguntas: (1.) Se "isso não é exatamente verdade", por que os documentos dizem categoricamente que é? e, (2.) Como, então, você o faz import stringse acidentalmente ocultá-lo, pelo menos sem passar por isso sys.modules. Não é isso que from __future__ import absolute_importse pretende impedir? O que isso faz? (PS, eu não sou o derrotador).
Alquimista de dois bits
14
Sim, era eu (voto negativo por 'não útil', não por 'errado'). Está claro na seção inferior que o OP entende como sys.pathfunciona, e a pergunta real ainda não foi abordada. Ou seja, o que from __future__ import absolute_importrealmente faz?
Wim
5
@ Two-BitAlchemist: 1) O changelog é vagamente redigido e não normativo. 2) Você para de sombrear. Mesmo vasculhar o código sys.modulesnão é o stringmódulo da biblioteca padrão se você o sombrear com seu próprio módulo de nível superior. from __future__ import absolute_importnão pretende impedir que os módulos de nível superior sombream os módulos de nível superior; deve parar os módulos internos do pacote de sombrear os módulos de nível superior. Se você executar o arquivo como parte do pkgpacote, os arquivos internos do pacote param de aparecer como de nível superior.
User2357112 suporta Monica
@ Two-BitAlchemist: Resposta revisada. Esta versão é mais útil?
user2357112 suporta Monica
1
@storen: Assumindo que pkgé um pacote no caminho de pesquisa de importação, deveria ser python -m pkg.main. -mprecisa de um nome de módulo, não de um caminho de arquivo.
User2357112 suporta Monica
44

A diferença entre importações absolutas e relativas entra em jogo somente quando você importa um módulo de um pacote e esse módulo importa um outro submódulo desse pacote. Veja a diferença:

$ mkdir pkg
$ touch pkg/__init__.py
$ touch pkg/string.py
$ echo 'import string;print(string.ascii_uppercase)' > pkg/main1.py
$ python2
Python 2.7.9 (default, Dec 13 2014, 18:02:08) [GCC] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pkg.main1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "pkg/main1.py", line 1, in <module>
    import string;print(string.ascii_uppercase)
AttributeError: 'module' object has no attribute 'ascii_uppercase'
>>> 
$ echo 'from __future__ import absolute_import;import string;print(string.ascii_uppercase)' > pkg/main2.py
$ python2
Python 2.7.9 (default, Dec 13 2014, 18:02:08) [GCC] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pkg.main2
ABCDEFGHIJKLMNOPQRSTUVWXYZ
>>> 

Em particular:

$ python2 pkg/main2.py
Traceback (most recent call last):
  File "pkg/main2.py", line 1, in <module>
    from __future__ import absolute_import;import string;print(string.ascii_uppercase)
AttributeError: 'module' object has no attribute 'ascii_uppercase'
$ python2
Python 2.7.9 (default, Dec 13 2014, 18:02:08) [GCC] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pkg.main2
ABCDEFGHIJKLMNOPQRSTUVWXYZ
>>> 
$ python2 -m pkg.main2
ABCDEFGHIJKLMNOPQRSTUVWXYZ

Observe que o python2 pkg/main2.pycomportamento é diferente ao iniciar python2e depois importar pkg.main2(o que equivale a usar a -mopção).

Se você quiser executar um submódulo de um pacote, use sempre a -mopção que impede o intérprete de encadear a sys.pathlista e manipula corretamente a semântica do submódulo.

Além disso, prefiro usar importações relativas explícitas para sub-módulos de pacotes, pois fornecem mais semântica e melhores mensagens de erro em caso de falha.

Bakuriu
fonte
Então, basicamente, ele funciona apenas em casos limitados em que você evitou o problema do "diretório atual"? Essa parece ser uma implementação muito mais fraca do que a descrita pelo PEP 328 e pelo changelog 2.5. Você acredita que a documentação é imprecisa?
Two-Bit Alchemist
@ Dois-BitAlchemist Na verdade, o que você está fazendo é o "caso restrito". Você inicia apenas um único arquivo python a ser executado, mas isso pode acionar centenas de importações. Os submódulos de um pacote simplesmente não devem ser executados, só isso.
Bakuriu 16/11/2015
por que python2 pkg/main2.pytem um comportamento diferente ao iniciar o python2 e depois importar o pkg.main2?
Støren
1
@storen Isso ocorre porque o comportamento das importações relativas muda. Quando você inicia o pkg/main2.pypython (versão 2) não trata pkgcomo um pacote. Enquanto estiver usando python2 -m pkg.main2ou importá-lo fazer levar em conta que pkgé um pacote.
Bakuriu 11/04