Como corrigir "Tentativa de importação relativa em não pacote", mesmo com __init__.py

744

Estou tentando seguir o PEP 328 , com a seguinte estrutura de diretórios:

pkg/
  __init__.py
  components/
    core.py
    __init__.py
  tests/
    core_test.py
    __init__.py

Em core_test.pyeu tenho a seguinte declaração de importação

from ..components.core import GameLoopEvents

No entanto, quando executo, recebo o seguinte erro:

tests$ python core_test.py 
Traceback (most recent call last):
  File "core_test.py", line 3, in <module>
    from ..components.core import GameLoopEvents
ValueError: Attempted relative import in non-package

Pesquisando, encontrei " caminho relativo que não funciona mesmo com __init__.py " e " Importar um módulo de um caminho relativo ", mas eles não ajudaram.

Falta alguma coisa aqui?

skytreader
fonte
17
Também fiquei muito confuso com as várias maneiras de estruturar unittestprojetos, por isso escrevi este projeto de amostra bastante exaustivo que abrange o aninhamento profundo de módulos, importações relativas e absolutas (onde o trabalho não funciona) e referências relativas e absolutas de dentro de um pacote, bem como importação única, dupla e no nível de pacote. Ajudou as coisas claras até para mim!
Cod3monk3y
1
Não consegui que seus testes funcionassem. Continue recebendo no module named myimports.fooquando eu os corro.
Blairg23
@ Blairg23 Acho que a invocação pretendida é cdentrar PyImportse executar python -m unittest tests.test_abs, por exemplo.
duozmo
7
Eu concordo com Gene. Eu gostaria que houvesse um mecanismo para depurar o processo de importação que fosse um pouco mais útil. No meu caso, tenho dois arquivos no mesmo diretório. Estou tentando importar um arquivo para o outro arquivo. Se eu tiver um arquivo .py de init nesse diretório, recebo uma importação relativa ValueError: Tentativa relativa em erro que não é do pacote. Se eu remover o arquivo .py init , recebo um erro no módulo chamado erro 'NAME'.
user1928764
No meu caso, tenho dois arquivos no mesmo diretório. Estou tentando importar um arquivo para o outro arquivo. Se eu tiver um arquivo .py de init nesse diretório, recebo uma importação relativa ValueError: Tentativa relativa em erro que não é do pacote. Se eu remover o arquivo .py init , recebo um erro no módulo chamado erro 'NAME'. O que é realmente frustrante é que eu tinha esse trabalho, e então me atirei excluindo o arquivo .bashrc, que definiu o PYTHONPATH para algo, e agora não está funcionando.
user1928764

Respostas:

443

Sim. Você não o está usando como pacote.

python -m pkg.tests.core_test
Ignacio Vazquez-Abrams
fonte
51
Uma dica: observe que não há '.py' no final!
mindthief
497
Eu não sou um dos menos votantes, mas acho que isso poderia usar um pouco mais de detalhes, dada a popularidade desta pergunta e resposta. Observando coisas como em qual diretório executar o comando shell acima, o fato de que você precisa estar __init__.pytodo o tempo e os __package__truques de modificação (descritos abaixo por BrenBarn) necessários para permitir essas importações para scripts executáveis ​​(por exemplo, ao usar um shebang e fazendo ./my_script.pyno shell do Unix) tudo seria útil. Toda essa questão foi bastante complicada para eu descobrir ou encontrar documentação concisa e compreensível.
Mark Amery
16
Nota: você precisa estar fora do diretório pkgno ponto em que chama esta linha a partir da CLI. Então, deve funcionar como esperado. Se você estiver dentro pkge ligar python -m tests.core_test, não funcionará. Pelo menos não aconteceu comigo.
Blairg23
94
Sério, você pode explicar o que está acontecendo na sua resposta?
Pinóquio
18
@ MarkAmery Quase perdi a cabeça tentando entender como tudo isso funciona, importações relativas em um projeto com subdiretórios com arquivos py que possuem __init__.pyarquivos, mas você continua recebendo o ValueError: Attempted relative import in non-packageerro. Eu pagaria um dinheiro muito bom para alguém, em algum lugar, para finalmente explicar em inglês simples como tudo isso funciona.
precisa saber é o seguinte
635

Para elaborar a resposta de Ignacio Vazquez-Abrams :

O mecanismo de importação do Python funciona em relação ao __name__arquivo atual. Quando você executa um arquivo diretamente, ele não tem o nome habitual, mas "__main__"o nome. Portanto, as importações relativas não funcionam.

Você pode, como Igancio sugeriu, executá-lo usando a -mopção Se você tem uma parte do seu pacote que deve ser executada como um script, também pode usar o __package__atributo para informar ao arquivo que nome ele deve ter na hierarquia de pacotes.

Veja http://www.python.org/dev/peps/pep-0366/ para obter detalhes.

BrenBarn
fonte
55
Demorou um pouco para eu perceber que você não pode executar python -m core_testde dentro do testssubdiretório - ele deve ser do pai ou você deve adicionar o pai ao caminho.
Aram Kocharyan
3
@ DannyStaple: Não exatamente. Você pode usar __package__para garantir que os arquivos de script executáveis ​​possam importar relativamente outros módulos de dentro do mesmo pacote. Não há como importar relativamente de "todo o sistema". Nem sei por que você faria isso.
BrenBarn
2
Quero dizer, se o __package__símbolo estiver definido como "parent.child", você poderá importar "parent.other_child". Talvez eu não tenha dito isso tão bem.
9139 Danny Staple
5
@ DannyStaple: Bem, como funciona está descrito na documentação vinculada. Se você tem um script script.pyno pacote pack.subpack, em seguida, defini-lo é __package__a pack.subpackvai deixar você fazer from ..module import somethinga importação algo pack.module. Observe que, como diz a documentação, você ainda precisa ter o pacote de nível superior no caminho do sistema. Já é assim que as coisas funcionam para os módulos importados. A única coisa a __package__fazer é permitir que você use esse comportamento também para scripts executados diretamente.
BrenBarn 10/07
3
Eu uso __package__no script que é executado diretamente, mas infelizmente, recebo o seguinte erro: "O módulo pai 'xxx' não foi carregado, não pode executar importação relativa"
mononoke
202

Você pode usar import components.corediretamente se anexar o diretório atual a sys.path:

if __name__ == '__main__' and __package__ is None:
    from os import sys, path
    sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))
ihm
fonte
35
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))Isso também irá trabalhar
ajay
26
from os import sysolhares como batota :)
ovelha voando
3
@Piotr: É pode ser considerado melhor porque ligeiramente mostra mais claramente o que está sendo sendo anexado à sys.path- o pai do diretório do arquivo atual está em.
martineau
8
@ flyingsheep: Concordo, eu apenas usaria um regular import sys, os.path as path.
martineau
10
FYI, para usar isso em um notebook ipython, eu adaptei esta resposta para: import os; os.sys.path.append(os.path.dirname(os.path.abspath('.'))). Então, um straight import components.corefunciona para mim, importando do diretório pai do notebook, conforme desejado.
Racing Tadpole
195

Depende de como você deseja iniciar seu script.

Se você deseja iniciar seu UnitTest a partir da linha de comando de maneira clássica, ou seja:

python tests/core_test.py

Então, como nesse caso 'componentes' e 'testes' são pastas irmãos, você pode importar o módulo relativo usando a inserção ou o append do módulo sys.path . Algo como:

import sys
from os import path
sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
from components.core import GameLoopEvents

Caso contrário, você poderá iniciar seu script com o argumento '-m' (observe que, neste caso, estamos falando de um pacote e, portanto, você não deve fornecer a extensão '.py' ), ou seja:

python -m pkg.tests.core_test

Nesse caso, você pode simplesmente usar a importação relativa como estava fazendo:

from ..components.core import GameLoopEvents

Você pode finalmente misturar as duas abordagens, para que seu script funcione, independentemente de como é chamado. Por exemplo:

if __name__ == '__main__':
    if __package__ is None:
        import sys
        from os import path
        sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
        from components.core import GameLoopEvents
    else:
        from ..components.core import GameLoopEvents
Paolo Rovelli
fonte
3
o que devo fazer se estiver tentando usar o pdb para depuração? desde que você usa python -m pdb myscript.pypara iniciar a sessão de depuração.
danny
1
@dannynjust - Essa é uma boa pergunta, já que você não pode ter 2 módulos principais. Geralmente, ao depurar, prefiro soltar no depurador manualmente no primeiro ponto em que desejo iniciar a depuração. Você pode fazer isso inserindo um import pdb; pdb.set_trace()no código (embutido).
mgilson
3
É melhor usar em insertvez de append? Ou seja,sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
SparkAndShine
2
O uso de insert é uma correspondência melhor para a semântica de importação relativa, onde os nomes dos pacotes locais têm precedência sobre os pacotes instalados. Especialmente para testes, geralmente você deseja testar a versão local, não a instalada (a menos que sua infraestrutura de teste instale o código em teste, nesse caso, as importações relativas não são necessárias e você não terá esse problema).
AlexDupuy
1
você também deve mencionar que você não pode estar no diretório que contém core_test quando executar como um módulo (que seria muito fácil)
Joseph Garvin
25

No core_test.py, faça o seguinte:

import sys
sys.path.append('../components')
from core import GameLoopEvents
Allan Mwesigwa
fonte
10

Se o seu caso de uso for para executar testes e parecer que é, você poderá fazer o seguinte. Em vez de executar seu script de teste, python core_test.pyuse uma estrutura de teste como pytest. Em seguida, na linha de comando, você pode inserir

$$ py.test

Isso executará os testes no seu diretório. Isso contorna a questão do __name__ser __main__que foi apontada pelo @BrenBarn. Em seguida, coloque um __init__.pyarquivo vazio no diretório de teste, isso fará com que o diretório de teste faça parte do seu pacote. Então você será capaz de fazer

from ..components.core import GameLoopEvents

No entanto, se você executar o script de teste como um programa principal, as coisas irão falhar novamente. Então, basta usar o corredor de teste. Talvez isso também funcione com outros corredores de teste, como nosetestsmas não o verifiquei. Espero que isto ajude.

deepak
fonte
9

Minha solução rápida é adicionar o diretório ao caminho:

import sys
sys.path.insert(0, '../components/')
v4gil
fonte
6
Sua abordagem não funcionará em todos os casos porque a parte '../' é resolvida no diretório a partir do qual você executa seu script (core_test.py). Com sua abordagem, você é forçado a fazer o cd para 'tests' antes de executar o scritp core_test.py.
Xyman
7

O problema está no seu método de teste,

você tentou python core_test.py

você receberá este erro ValueError: tentativa de importação relativa em pacote não

Razão: você está testando sua embalagem a partir de fontes que não são de pacote.

então teste seu módulo a partir da fonte do pacote.

se essa é a estrutura do seu projeto,

pkg/
  __init__.py
  components/
    core.py
    __init__.py
  tests/
    core_test.py
    __init__.py

cd pkg

python -m tests.core_test # dont use .py

ou de fora do pacote /

python -m pkg.tests.core_test

único .se você deseja importar da pasta no mesmo diretório. para cada passo atrás, adicione mais um.

hi/
  hello.py
how.py

no how.py

from .hi import hello

caso você queira importar como do hello.py

from .. import how
Mohideen bin Mohammed
fonte
1
Resposta melhor do que aceita
GabrielBB 03/02
No exemplo from .. import how, como você importa uma classe / método específico do arquivo 'como'. quando faço o equivalente a from ..how import foo, recebo "tentativa de importação relativa além do pacote de nível superior"
James Hulse
3

Fio antigo. Descobri que adicionar um arquivo __all__= ['submodule', ...]ao __init__.py e depois usá-lo from <CURRENT_MODULE> import *no destino funciona bem.

Laurent
fonte
3

Você pode usar from pkg.components.core import GameLoopEvents, por exemplo, uso pycharm, a seguir é minha imagem da estrutura do projeto, apenas importo a partir do pacote raiz e funciona:

insira a descrição da imagem aqui

Jayhello
fonte
3
Isto não funcionou para mim. Você precisou definir o caminho na sua configuração?
Mohammad Mahjoub
3

Como Paolo disse, temos 2 métodos de chamada:

1) python -m tests.core_test
2) python tests/core_test.py

Uma diferença entre eles é sys.path [0] string. Como a interpretação pesquisará sys.path ao importar , podemos fazer com tests/core_test.py:

if __name__ == '__main__':
    import sys
    from pathlib import Path
    sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
    from components import core
    <other stuff>

E mais, depois disso, podemos executar o core_test.py com outros métodos:

cd tests
python core_test.py
python -m core_test
...

Observe que o py36 foi testado apenas.

zhengcao
fonte
3

Essa abordagem funcionou para mim e é menos confusa do que algumas soluções:

try:
  from ..components.core import GameLoopEvents
except ValueError:
  from components.core import GameLoopEvents

O diretório pai está no meu PYTHONPATH e há __init__.pyarquivos no diretório pai e neste diretório.

O exemplo acima sempre funcionou no python 2, mas o python 3 às vezes atinge um ImportError ou ModuleNotFoundError (o último é novo no python 3.6 e uma subclasse de ImportError), portanto, o seguinte ajuste funciona para mim no python 2 e 3:

try:
  from ..components.core import GameLoopEvents
except ( ValueError, ImportError):
  from components.core import GameLoopEvents
Rick Graves
fonte
2

Tente isto

import components
from components import *
Vaishnavi Bala
fonte
1

Se alguém está procurando uma solução alternativa, eu me deparei com uma. Aqui está um pouco de contexto. Eu queria testar um dos métodos que tenho em um arquivo. Quando eu corro de dentro

if __name__ == "__main__":

sempre reclamou das importações relativas. Tentei aplicar as soluções acima, mas não funcionou, pois havia muitos arquivos aninhados, cada um com várias importações.

Aqui está o que eu fiz. Acabei de criar um lançador, um programa externo que importaria os métodos necessários e os chamaria. Embora não seja uma ótima solução, ele funciona.

HappyWaters
fonte
0

Aqui está uma maneira de irritar todos, mas funciona muito bem. Nos testes executados:

ln -s ../components components

Em seguida, basta importar componentes como faria normalmente.

SteveCalifornia
fonte
0

Isso é muito confuso, e se você estiver usando IDE como pycharm, é um pouco mais confuso. O que funcionou para mim: 1. Faça as configurações do projeto pycharm (se você estiver executando o python a partir de um VE ou do diretório python) 2. Não há nada errado da maneira que você definiu. em algum momento ele funciona com a classe de importação folder1.file1

se não funcionar, use import folder1.file1 3. Sua variável de ambiente deve ser mencionada corretamente no sistema ou fornecê-la no seu argumento de linha de comando.

SANTI SANTOSH MAHAPATRA
fonte
-2

Como seu código contém if __name__ == "__main__", que não é importado como um pacote, é melhor usá-lo sys.path.append()para resolver o problema.

rosefun
fonte
Eu não acho que ter if __name__ == "__main__"em seu arquivo faça diferença em qualquer coisa relacionada à importação.
user48956