além do erro do pacote de nível superior na importação relativa


317

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 packagee 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 packagee executo o módulo dentro do subpacote test_A test_A.test, pois , com base no meu entendimento, ..Asobe apenas um nível, que ainda está dentro da packagepasta, por que ele diz uma mensagem beyond top-level package. Qual é exatamente o motivo que causa essa mensagem de erro?


49
esse post não explicou meu erro "além do pacote de nível superior"
shelper

4
Eu tenho um pensamento aqui, então, quando executar test_A.test como módulo, '..' ultrapassa test_A, que já é o nível mais alto da importação test_A.test, acho que o nível do pacote não é o nível do diretório, mas quantos níveis que você importa o pacote.
abrigo

2
Prometo que você entenderá tudo sobre a importação relativa depois de assistir a esta resposta stackoverflow.com/a/14132912/8682868 .
precisa saber é

consulte ValueError: tentativa de importação relativa além do pacote de nível superior para obter explicações detalhadas sobre esse problema.
Napuzba 17/09/19

Existe uma maneira de evitar importações relativas? Como a maneira como o PyDev no Eclipse vê todos os pacotes dentro do <PydevProject> / src?
Mushu909

Respostas:


173

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 que test_A.testestá realmente armazenado package(isto packageé, não é considerado um pacote). Tentativa de from ..A import footentar acessar informações que não possuem mais (por exemplo, diretórios irmãos de um local carregado). É conceitualmente semelhante a permitir a entrada from ..os import pathde um arquivo math. 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 com from os import pathe deixar o python descobrir onde está $PATHe $PYTHONPATH.

Quando você usa python -m package.test_A.test, o uso de from ..A import fooresolve é muito bom, porque ele acompanha o conteúdo packagee 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.


2
Editei minha resposta para me referir a uma resposta melhor a uma pergunta que equivale à mesma coisa. Existem apenas soluções alternativas. A única coisa que eu realmente vi funcionar é o que o OP fez, que é usar o -msinalizador e executar no diretório acima.
Multihunter

1
Note-se que esta resposta , a partir do link fornecido pelo Multihunter, não envolve o sys.pathhack, mas o uso de setuptools , o que é muito mais interessante na minha opinião.
Angelo Cardellicchio

157
import sys
sys.path.append("..") # Adds higher directory to python modules path.

Tente isso. Trabalhou para mim.


10
Umm ... como isso funcionaria? Cada arquivo de teste teria isso?
George Mauer

O problema aqui é se, por exemplo, A/bar.pyexiste e foo.pyvocê o faz from .bar import X.
user1834164

9
Eu tive que remover o .. de "de .. Uma importação ..." depois de adicionar o sys.path.append ("..") #
334 Jake OPJ

2
Se o script for executado fora do diretório existente, isso não funcionará. Em vez disso, você deve ajustar esta resposta para especificar o caminho absoluto do referido script .
Manavalan Gajapathy

esta é a melhor opção, menos complicada
Alex R

43

Suposição:
se você estiver no packagediretório Ae test_Afor pacotes separados.

Conclusão: as
..Aimportaçõ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:

Eu sou o único que acha que isso é loucura !? Por que no mundo o diretório de trabalho atual não é considerado um pacote? - Multihunter

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.


86
Eu sou o único que acha que isso é loucura !? Por que no mundo o diretório em execução não é considerado um pacote?
Multihunter

13
Isso não é apenas insano, isso é inútil ... então como você executa os testes? Claramente, o que o OP estava perguntando e por que tenho certeza de que muitas pessoas também estão aqui.
George Mauer

O diretório em execução 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. - resposta editada.
Utilizador

Eu não sigo a inconsistência. O comportamento de python -m package.test_A.testparece 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?
Multihunter

Na verdade, estou pensando, há uma solicitação de recurso para isso? Isso é realmente insano. O estilo C / C ++ #includeseria muito útil!
Nicholas Humphrey

29

Nenhuma dessas soluções funcionou para mim na 3.6, com uma estrutura de pastas como:

package1/
    subpackage1/
        module1.py
package2/
    subpackage2/
        module2.py

Meu objetivo era importar do módulo1 para o módulo2. O que finalmente funcionou para mim foi, curiosamente:

import sys
sys.path.append(".")

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:

import os
print (os.getcwd())

No meu caso, o diretório de trabalho foi (inesperadamente) a raiz do projeto.


2
está funcionando localmente, mas não está funcionando na instância aws ec2, faz algum sentido?
thebeancounter 27/02/19

Isso funcionou para mim também - no meu caso, o diretório de trabalho também era a raiz do projeto. Eu estava usando um atalho prazo de um editor de programação (TextMate)
JeremyDouglass

@thebeancounter Same! Funciona localmente no meu mac, mas não funciona no ec2, então percebi que estava executando o comando em um subdiretório no ec2 e executando-o na raiz localmente. Depois de executá-lo a partir da raiz no ec2, funcionou.
Logan Yang

Isso também funcionou para mim, muito apreciado. A partir desse método sys agora posso simplesmente chamar o pacote sem a necessidade de ".."
RamWill

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.
KevinZhou 20/01

13

from package.A import foo

Eu acho que é mais claro do que

import sys
sys.path.append("..")

4
é mais legível, com certeza, mas ainda precisa sys.path.append(".."). testado em python 3.6
MFA

O mesmo que as respostas anteriores
nrofis 19/12/19

12

Como sugere a resposta mais popular, basicamente é porque você PYTHONPATHou sys.pathinclui, .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:

PYTHONPATH=/path/to/package python -m test_A.test

OU forçando o caminho python quando chamado dessa maneira, porque:

Com python -m test_A.testvocê está executando test_A/test.pycom __name__ == '__main__'e__file__ == '/absolute/path/to/test_A/test.py'

Isso significa que test.pyvocê pode usar seu absoluto importsemiprotegido na condição de caso principal e também fazer alguma manipulação de caminho Python única:

from os import path

def main():

if __name__ == '__main__':
    import sys
    sys.path.append(path.join(path.dirname(__file__), '..'))
    from A import foo

    exit(main())

8

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:

"O mesmo pode ser especificado programaticamente desta maneira:

sys de importação

sys.path.append ('..')

Obviamente, o código acima deve ser escrito antes da outra declaração de importação .

É 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.


o link que você postou está morto.
baxx

Obrigado por me avisar. Parece que o nome de domínio não é mais controlado pela mesma pessoa. Eu removi o link.
Mierpo

5

se você tiver um __init__.pyem uma pasta superior, poderá inicializar a importação como import file/path as aliasnesse arquivo init. Então você pode usá-lo em scripts inferiores como:

import alias

0

Na minha humilde opinião, entendo esta questão da seguinte maneira:

[CASO 1] Quando você inicia uma importação absoluta como

python -m test_A.test

ou

import test_A.test

ou

from test_A import test

na verdade, você está configurando a âncora de importação como sendo test_A, em outras palavras, o pacote de nível superior test_A. Portanto, quando temos test.py do from ..A import xxx, você está escapando da âncora e o Python não permite isso.

[CASO 2] Quando você faz

python -m package.test_A.test

ou

from package.test_A import test

sua âncora se torna package, package/test_A/test.pyfazendo from ..A import xxxisso não escapa da âncora (ainda dentro da packagepasta) e o Python aceita isso com satisfação.

Em resumo:

  • A importação absoluta altera a âncora atual (= redefine o que é o pacote de nível superior);
  • A importação relativa não altera a âncora, mas restringe-se a ela.

Além disso, podemos usar o nome completo do módulo (FQMN) para inspecionar esse problema.

Verifique o FQMN em cada caso:

  • [CASE2] test.__name__=package.test_A.test
  • [CASE1] test.__name__=test_A.test

Portanto, para CASE2, um from .. import xxxresultará em um novo módulo com FQMN = package.xxx, que é aceitável.

Enquanto para CASE1, o ..de dentro from .. import xxxpulará do nó inicial (âncora) de test_A, e isso NÃO é permitido pelo Python.


2
Isso é muito mais complicado do que precisa ser. Tanto para Zen of Python.
AtilioA

0

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

Diretório de nível superior do projeto (o padrão é o diretório inicial)

Então, em uma estrutura como

project_root
  |
  |----- my_module
  |          \
  |           \_____ my_class.py
  |
  \ tests
      \___ test_my_func.py

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_classsem grandes dramas.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.