top - python import module




Importações relativas pela bilionésima vez (6)

As importações relativas usam o atributo name de um módulo para determinar a posição desse módulo na hierarquia de pacotes. Se o nome do módulo não contiver nenhuma informação de pacote (por exemplo, ele está configurado como 'main'), as importações relativas serão resolvidas como se o módulo fosse um módulo de nível superior, independentemente de onde o módulo está realmente localizado no sistema de arquivos.

Escreveu um pequeno pacote python para o PyPi que pode ajudar os espectadores desta questão. O pacote atua como solução alternativa se você quiser poder executar arquivos python contendo importações contendo pacotes de nível superior de dentro de um pacote / projeto sem estar diretamente no diretório do arquivo de importação. https://pypi.org/project/import-anywhere/

Eu estive aqui:

e muitas URLs que eu não copiei, algumas em SO, outras em outros sites, quando pensei em ter a solução rapidamente.

A pergunta sempre recorrente é: com o Windows 7, Python 2.7.3 de 32 bits, como resolvo essa mensagem "Importação relativa à tentativa em um pacote não"? Eu construí uma réplica exata do pacote em pep-0328:

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
        moduleY.py
    subpackage2/
        __init__.py
        moduleZ.py
    moduleA.py

Eu fiz funções chamadas spam e ovos em seus módulos apropriados. Naturalmente, não funcionou. A resposta está aparentemente na quarta URL que listei, mas é tudo para mim. Houve essa resposta em uma das URLs que visitei:

As importações relativas usam o atributo name de um módulo para determinar a posição desse módulo na hierarquia de pacotes. Se o nome do módulo não contiver nenhuma informação de pacote (por exemplo, ele está configurado como 'main'), as importações relativas serão resolvidas como se o módulo fosse um módulo de nível superior, independentemente de onde o módulo está realmente localizado no sistema de arquivos.

A resposta acima parece promissora, mas é tudo hieróglifos para mim. Então, minha pergunta, como faço com que o Python não retorne para mim "Attempted relative import in non-package"? tem uma resposta que envolve -m, supostamente.

Alguém pode me dizer por que o Python dá essa mensagem de erro, o que significa por não-pacote! , por que e como você define um 'pacote', e a resposta precisa colocar em termos fáceis o suficiente para que um kindergartener entenda .

Edit: As importações foram feitas a partir do console.


Aqui está uma receita geral, modificada para caber como um exemplo, que estou usando agora para lidar com bibliotecas Python escritas como pacotes, que contêm arquivos interdependentes, onde eu quero poder testar partes deles em partes. Vamos chamar isso de lib.foo e dizer que ele precisa de acesso ao lib.fileA para as funções f1 e f2 e lib.fileB para a classe Class3 .

Incluí algumas chamadas de print para ajudar a ilustrar como isso funciona. Na prática, você gostaria de removê-los (e talvez também da linha from __future__ import print_function ).

Este exemplo em particular é simples demais para mostrar quando realmente precisamos inserir uma entrada no sys.path . (Veja a resposta de Lars para um caso em que precisamos, quando temos dois ou mais níveis de diretórios de pacotes, e então usamos os.path.dirname(os.path.dirname(__file__)) - mas isso não acontece. realmente doer aqui também.) Também é seguro o suficiente para fazer isso sem o teste if _i in sys.path . No entanto, se cada arquivo importado inserir o mesmo caminho - por exemplo, se o fileA e o fileA fileB quiserem importar utilitários do pacote - isso sobrecarregará o sys.path com o mesmo caminho várias vezes, por isso é bom ter o if _i not in sys.path no clichê.

from __future__ import print_function # only when showing how this works

if __package__:
    print('Package named {!r}; __name__ is {!r}'.format(__package__, __name__))
    from .fileA import f1, f2
    from .fileB import Class3
else:
    print('Not a package; __name__ is {!r}'.format(__name__))
    # these next steps should be used only with care and if needed
    # (remove the sys.path manipulation for simple cases!)
    import os, sys
    _i = os.path.dirname(os.path.abspath(__file__))
    if _i not in sys.path:
        print('inserting {!r} into sys.path'.format(_i))
        sys.path.insert(0, _i)
    else:
        print('{!r} is already in sys.path'.format(_i))
    del _i # clean up global name space

    from fileA import f1, f2
    from fileB import Class3

... all the code as usual ...

if __name__ == '__main__':
    import doctest, sys
    ret = doctest.testmod()
    sys.exit(0 if ret.failed == 0 else 1)

A ideia aqui é esta (e note que todas elas funcionam da mesma forma em python2.7 e python 3.x):

  1. Se executado como import lib ou from lib import foo como uma importação de pacote regular do código comum, __package é lib e __name__ é lib.foo . Tomamos o primeiro caminho de código, importando de .fileA , etc.

  2. Se executado como python lib/foo.py , __package__ será None e __name__ será __main__ .

    Nós tomamos o segundo caminho de código. O diretório lib já estará em sys.path então não há necessidade de adicioná-lo. Nós importamos do fileA , etc.

  3. Se executado dentro do diretório lib como python foo.py , o comportamento é o mesmo que para o caso 2.

  4. Se executado dentro do diretório lib como python -m foo , o comportamento é semelhante aos casos 2 e 3. No entanto, o caminho para o diretório lib não está em sys.path , portanto, o adicionamos antes de importar. O mesmo se aplica se nós rodamos o Python e então import foo .

    (Já que o . Está no sys.path , nós realmente não precisamos adicionar a versão absoluta do caminho aqui. Aqui é onde uma estrutura de aninhamento de pacotes mais profunda, onde queremos fazer a from ..otherlib.fileC import ... , faz a diferença. Se você não está fazendo isso, você pode omitir toda a manipulação do sys.path completamente.)

Notas

Ainda há um capricho. Se você executar tudo isso de fora:

$ python2 lib.foo

ou:

$ python3 lib.foo

o comportamento depende do conteúdo de lib/__init__.py . Se isso existe e está vazio , tudo está bem:

Package named 'lib'; __name__ is '__main__'

Mas se lib/__init__.py ele mesmo importar routine para que possa exportar routine.name diretamente como lib.name , você obtém:

$ python2 lib.foo
Package named 'lib'; __name__ is 'lib.foo'
Package named 'lib'; __name__ is '__main__'

Ou seja, o módulo é importado duas vezes, uma vez através do pacote e, em seguida, novamente como __main__ para que ele execute seu código main . O Python 3.6 e posterior avisa sobre isso:

$ python3 lib.routine
Package named 'lib'; __name__ is 'lib.foo'
[...]/runpy.py:125: RuntimeWarning: 'lib.foo' found in sys.modules
after import of package 'lib', but prior to execution of 'lib.foo';
this may result in unpredictable behaviour
  warn(RuntimeWarning(msg))
Package named 'lib'; __name__ is '__main__'

O aviso é novo, mas o comportamento de aviso não é. É parte do que alguns chamam de armadilha de importação dupla . (Para detalhes adicionais veja a edição 27487. ) Nick Coghlan diz:

Esta próxima armadilha existe em todas as versões atuais do Python, incluindo 3.3, e pode ser resumida na seguinte diretriz geral: "Nunca adicione um diretório de pacotes, ou qualquer diretório dentro de um pacote, diretamente ao caminho do Python".

Observe que, embora violemos essa regra aqui, fazemos isso apenas quando o arquivo que está sendo carregado não está sendo carregado como parte de um pacote, e nossa modificação é especificamente projetada para nos permitir acessar outros arquivos nesse pacote. (E, como observei, provavelmente não deveríamos fazer isso em pacotes de nível único.) Se quiséssemos ser mais limpos, poderíamos reescrever isso como, por exemplo:

    import os, sys
    _i = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    if _i not in sys.path:
        sys.path.insert(0, _i)
    else:
        _i = None

    from sub.fileA import f1, f2
    from sub.fileB import Class3

    if _i:
        sys.path.remove(_i)
    del _i

Ou seja, modificamos o sys.path tempo suficiente para alcançar nossas importações, depois as colocamos de volta como estavam (excluindo uma cópia de _i se e somente se adicionássemos uma cópia de _i ).


Então, depois de falar sobre isso junto com muitos outros, me deparei com uma nota postada por Dorian B neste article que resolveu o problema específico que eu estava tendo, onde desenvolveria módulos e classes para uso com um serviço web, mas também quero ser capaz de testá-los como estou codificando, usando os recursos de depuração no PyCharm. Para executar testes em uma classe independente, incluirei o seguinte no final do meu arquivo de classe:

if __name__ == '__main__':
   # run test code here...

mas se eu quisesse importar outras classes ou módulos na mesma pasta, teria que alterar todas as minhas instruções de importação de notação relativa para referências locais (ou seja, remover o ponto (.)) Mas depois de ler a sugestão de Dorian, tentei one-liner 'e funcionou! Agora posso testar no PyCharm e deixar meu código de teste no lugar quando eu uso a classe em outra classe em teste, ou quando eu a uso no meu serviço web!

# import any site-lib modules first, then...
import sys
parent_module = sys.modules['.'.join(__name__.split('.')[:-1]) or '__main__']
if __name__ == '__main__' or parent_module.__name__ == '__main__':
    from codex import Codex # these are in same folder as module under test!
    from dblogger import DbLogger
else:
    from .codex import Codex
    from .dblogger import DbLogger

A instrução if verifica se estamos executando este módulo como principal ou se está sendo usado em outro módulo que está sendo testado como principal . Talvez isso seja óbvio, mas ofereço esta nota aqui caso alguém mais frustrado com os problemas de importação relativos acima possa utilizá-la.


Este é realmente um problema dentro do python. A origem da confusão é que as pessoas tomam erroneamente a importação relativa como caminho relativo, o que não é.

Por exemplo, quando você escreve em faa.py :

from .. import foo

Isso tem um significado somente se faa.py foi identificado e carregado por python, durante a execução, como parte de um pacote. Nesse caso, o nome do módulo para faa.py seria, por exemplo, alguma_quadro_de_pacote.faa . Se o arquivo foi carregado apenas porque está no diretório atual, quando o python é executado, seu nome não se referirá a nenhum pacote e, eventualmente, a importação relativa falhará.

Uma solução simples para referenciar módulos no diretório atual é usar isto:

if __package__ is None or __package__ == '':
    # uses current directory visibility
    import foo
else:
    # uses current package visibility
    from . import foo

Para fazer o Python não retornar para mim "Attempted relative import in non-package". pacote/

init .py subpacote1 / init .py moduleX.py moduleY.py subpacote2 / init .py moduleZ.py moduleA.py

Este erro ocorre apenas se você estiver aplicando a importação relativa ao arquivo principal. Por exemplo, o arquivo principal já retorna main após o código "print ( name )" em moduleA.py .assim que o arquivo THIS já é main, ele não pode retornar nenhum pacote pai mais adiante. importações relativas são necessários em arquivos de pacotes subpackage1 e subpackage2 você pode usar ".." para se referir ao diretório pai ou módulo .Mas pai é se já pacote de nível superior não pode ir mais longe que o diretório pai (pacote). Esses arquivos em que você está aplicando a importação relativa aos pais só podem trabalhar com o aplicativo de importação absoluta. Se você usar o pacote ABSOLUTE IMPORT IN PARENT, não haverá ERRO, pois o Python saberá quem está no nível mais alto do pacote, mesmo que o arquivo esteja em subpacotes devido ao conceito de PYTHON PATH, que define o nível superior do projeto.


__name__ muda dependendo se o código em questão é executado no namespace global ou como parte de um módulo importado.

Se o código não estiver sendo executado no espaço global, __name__ será o nome do módulo. Se estiver sendo executado no namespace global - por exemplo, se você digitá-lo em um console, ou executar o módulo como um script usando python.exe yourscriptnamehere.py então __name__ se tornará "__main__" .

Você verá um monte de código python com if __name__ == '__main__' é usado para testar se o código está sendo executado a partir do namespace global - isso permite que você tenha um módulo que duplique como um script.

Você tentou fazer essas importações do console?





import