Parece que já existem algumas perguntas aqui sobre a importação relativa no python 3, mas depois de passar por muitas delas, ainda não encontrei a resposta para o meu problema. Então aqui está a questão.
Eu tenho um pacote mostrado abaixo
package/
__init__.py
A/
__init__.py
foo.py
test_A/
__init__.py
test.py
e eu tenho uma única linha no test.py:
from ..A import foo
agora, estou na pasta de package
e corro
python -m test_A.test
Eu recebi mensagem
"ValueError: attempted relative import beyond top-level package"
mas se eu estiver na pasta pai de package
, por exemplo, eu corro:
cd ..
python -m package.test_A.test
tudo está bem.
Agora, minha pergunta é:
quando estou na pasta de package
e executo o módulo dentro do subpacote test_A test_A.test
, pois , com base no meu entendimento, ..A
sobe apenas um nível, que ainda está dentro da package
pasta, por que ele diz uma mensagem beyond top-level package
. Qual é exatamente o motivo que causa essa mensagem de erro?
Respostas:
EDIT: Existem respostas melhores / mais coerentes para esta pergunta em outras perguntas:
Por que isso não funciona? É porque o python não registra de onde um pacote foi carregado. Então, quando você o faz
python -m test_A.test
, basicamente apenas descarta o conhecimento quetest_A.test
está realmente armazenadopackage
(istopackage
é, não é considerado um pacote). Tentativa defrom ..A import foo
tentar acessar informações que não possuem mais (por exemplo, diretórios irmãos de um local carregado). É conceitualmente semelhante a permitir a entradafrom ..os import path
de um arquivomath
. Isso seria ruim porque você deseja que os pacotes sejam distintos. Se eles precisam usar algo de outro pacote, eles devem se referir a eles globalmente comfrom os import path
e deixar o python descobrir onde está$PATH
e$PYTHONPATH
.Quando você usa
python -m package.test_A.test
, o uso defrom ..A import foo
resolve é muito bom, porque ele acompanha o conteúdopackage
e você está acessando um diretório filho de um local carregado.Por que o python não considera o diretório de trabalho atual um pacote? Sem pistas , mas caramba, seria útil.
fonte
-m
sinalizador e executar no diretório acima.sys.path
hack, mas o uso de setuptools , o que é muito mais interessante na minha opinião.Tente isso. Trabalhou para mim.
fonte
A/bar.py
existe efoo.py
você o fazfrom .bar import X
.Suposição:
se você estiver no
package
diretórioA
etest_A
for pacotes separados.Conclusão: as
..A
importações são permitidas apenas dentro de um pacote.Observações adicionais:
Tornar as importações relativas disponíveis apenas nos pacotes é útil se você deseja forçar que os pacotes possam ser colocados em qualquer caminho localizado em
sys.path
.EDITAR:
O diretório de trabalho atual geralmente está localizado em sys.path. Portanto, todos os arquivos são importáveis. Esse é um comportamento desde o Python 2, quando os pacotes ainda não existiam. Tornar o diretório em execução um pacote permitiria a importação de módulos como "import .A" e "import A", que seriam dois módulos diferentes. Talvez seja uma inconsistência a considerar.
fonte
python -m package.test_A.test
parece fazer o que é desejado, e meu argumento é que esse deve ser o padrão. Então, você pode me dar um exemplo dessa inconsistência?#include
seria muito útil!Nenhuma dessas soluções funcionou para mim na 3.6, com uma estrutura de pastas como:
Meu objetivo era importar do módulo1 para o módulo2. O que finalmente funcionou para mim foi, curiosamente:
Observe o ponto único em oposição às soluções de dois pontos mencionadas até agora.
Edit: O seguinte ajudou a esclarecer isso para mim:
No meu caso, o diretório de trabalho foi (inesperadamente) a raiz do projeto.
fonte
sys.path.append(".")
funcionou porque você está chamando-o no diretório pai, observe que.
sempre representa o diretório em que você executa o comando python.from package.A import foo
Eu acho que é mais claro do que
fonte
sys.path.append("..")
. testado em python 3.6Como sugere a resposta mais popular, basicamente é porque você
PYTHONPATH
ousys.path
inclui,.
mas não o caminho, para o seu pacote. E a importação relativa é relativa ao seu diretório de trabalho atual, não ao arquivo em que a importação ocorre; estranhamente.Você pode corrigir isso alterando primeiro sua importação relativa para absoluta e, em seguida, iniciando-a com:
OU forçando o caminho python quando chamado dessa maneira, porque:
Com
python -m test_A.test
você está executandotest_A/test.py
com__name__ == '__main__'
e__file__ == '/absolute/path/to/test_A/test.py'
Isso significa que
test.py
você pode usar seu absolutoimport
semiprotegido na condição de caso principal e também fazer alguma manipulação de caminho Python única:fonte
Edit: 2020-05-08: Parece que o site que citei não é mais controlado pela pessoa que escreveu o conselho, por isso estou removendo o link para o site. Obrigado por me avisar baxx.
Se alguém ainda está lutando um pouco após as ótimas respostas já fornecidas, encontrei conselhos em um site que não está mais disponível.
Citação essencial do site que mencionei:
É bastante óbvio que tem que ser assim, pensando nisso depois do fato. Eu estava tentando usar o sys.path.append ('..') nos meus testes, mas me deparei com o problema postado pelo OP. Ao adicionar a definição import e sys.path antes das minhas outras importações, consegui resolver o problema.
fonte
se você tiver um
__init__.py
em uma pasta superior, poderá inicializar a importação comoimport file/path as alias
nesse arquivo init. Então você pode usá-lo em scripts inferiores como:fonte
Na minha humilde opinião, entendo esta questão da seguinte maneira:
[CASO 1] Quando você inicia uma importação absoluta como
ou
ou
na verdade, você está configurando a âncora de importação como sendo
test_A
, em outras palavras, o pacote de nível superiortest_A
. Portanto, quando temos test.py dofrom ..A import xxx
, você está escapando da âncora e o Python não permite isso.[CASO 2] Quando você faz
ou
sua âncora se torna
package
,package/test_A/test.py
fazendofrom ..A import xxx
isso não escapa da âncora (ainda dentro dapackage
pasta) e o Python aceita isso com satisfação.Em resumo:
Além disso, podemos usar o nome completo do módulo (FQMN) para inspecionar esse problema.
Verifique o FQMN em cada caso:
test.__name__
=package.test_A.test
test.__name__
=test_A.test
Portanto, para CASE2, um
from .. import xxx
resultará em um novo módulo com FQMN =package.xxx
, que é aceitável.Enquanto para CASE1, o
..
de dentrofrom .. import xxx
pulará do nó inicial (âncora) detest_A
, e isso NÃO é permitido pelo Python.fonte
Não tenho certeza no python 2.x, mas no python 3.6, supondo que você esteja tentando executar o conjunto inteiro, basta usar
-t
Então, em uma estrutura como
Pode-se, por exemplo, usar:
python3 unittest discover -s /full_path/project_root/tests -t /full_path/project_root/
E ainda importe os
my_module.my_class
sem grandes dramas.fonte