Explicação
Do PEP 328
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 do pacote (por exemplo, está definido 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 esteja realmente localizado no sistema de arquivos.
Em algum momento, o PEP 338 entrou em conflito com o PEP 328 :
... importações relativas dependem de __name__ para determinar a posição do módulo atual na hierarquia de pacotes. Em um módulo principal, o valor de __name__ é sempre '__main__' , portanto as importações relativas explícitas sempre falham (pois funcionam apenas para um módulo dentro de um pacote)
e para resolver o problema, o PEP 366 introduziu a variável de nível superior __package__
:
Ao adicionar um novo atributo no nível do módulo, esse PEP permite que importações relativas funcionem automaticamente se o módulo for executado usando a
opção -m . Uma pequena quantidade de clichê no próprio módulo permitirá que as importações relativas funcionem quando o arquivo for executado pelo nome. [...] Quando [o atributo] estiver presente, as importações relativas serão baseadas nesse atributo, e não no módulo __name__ . [...] Quando o módulo principal é especificado por seu nome de arquivo, o atributo __package__ será definido como None . [...] Quando o sistema de importação encontra uma importação relativa explícita em um módulo sem __package__ definido (ou com ele definido como None), calcula e armazena o valor correto (__name __. rpartition ('.') [0] para módulos normais e __name__ para módulos de inicialização de pacotes)
(ênfase minha)
Se __name__
for '__main__'
, __name__.rpartition('.')[0]
retorna uma string vazia. É por isso que existe uma string vazia literal na descrição do erro:
SystemError: Parent module '' not loaded, cannot perform relative import
A parte relevante da PyImport_ImportModuleLevelObject
função do CPython :
if (PyDict_GetItem(interp->modules, package) == NULL) {
PyErr_Format(PyExc_SystemError,
"Parent module %R not loaded, cannot perform relative "
"import", package);
goto error;
}
O CPython gera essa exceção se não puder encontrar package
(o nome do pacote) em interp->modules
(acessível como sys.modules
). Como sys.modules
é "um dicionário que mapeia nomes de módulos para módulos que já foram carregados" , agora está claro que o módulo pai deve ser explicitamente importado de forma absoluta antes de executar a importação relativa .
Nota: O patch da edição 18018 adicionou outro if
bloco , que será executado antes do código acima:
if (PyUnicode_CompareWithASCIIString(package, "") == 0) {
PyErr_SetString(PyExc_ImportError,
"attempted relative import with no known parent package");
goto error;
} /* else if (PyDict_GetItem(interp->modules, package) == NULL) {
...
*/
Se package
(o mesmo que acima) for uma string vazia, a mensagem de erro será exibida.
ImportError: attempted relative import with no known parent package
No entanto, você verá isso apenas no Python 3.6 ou mais recente.
Solução 1: execute seu script usando -m
Considere um diretório (que é um pacote Python ):
.
├── package
│ ├── __init__.py
│ ├── module.py
│ └── standalone.py
Todos os arquivos no pacote começam com as mesmas 2 linhas de código:
from pathlib import Path
print('Running' if __name__ == '__main__' else 'Importing', Path(__file__).resolve())
Estou incluindo essas duas linhas apenas para tornar a ordem das operações óbvia. Podemos ignorá-los completamente, pois eles não afetam a execução.
__init__.py e module.py contêm apenas essas duas linhas (ou seja, elas estão efetivamente vazias).
standalone.py tenta adicionalmente importar module.py por meio de importação relativa:
from . import module # explicit relative import
Estamos bem cientes de que /path/to/python/interpreter package/standalone.py
irá falhar. No entanto, podemos executar o módulo com a -m
opção de linha de comando que "procurará sys.path
o módulo nomeado e executará seu conteúdo como o __main__
módulo" :
vaultah@base:~$ python3 -i -m package.standalone
Importing /home/vaultah/package/__init__.py
Running /home/vaultah/package/standalone.py
Importing /home/vaultah/package/module.py
>>> __file__
'/home/vaultah/package/standalone.py'
>>> __package__
'package'
>>> # The __package__ has been correctly set and module.py has been imported.
... # What's inside sys.modules?
... import sys
>>> sys.modules['__main__']
<module 'package.standalone' from '/home/vaultah/package/standalone.py'>
>>> sys.modules['package.module']
<module 'package.module' from '/home/vaultah/package/module.py'>
>>> sys.modules['package']
<module 'package' from '/home/vaultah/package/__init__.py'>
-m
faz todo o material de importação para você e define automaticamente __package__
, mas você pode fazer isso sozinho no
Solução 2: Defina __package__ manualmente
Trate-o como uma prova de conceito e não como uma solução real. Não é adequado para uso em código do mundo real.
O PEP 366 tem uma solução alternativa para esse problema, no entanto, é incompleto, porque a configuração __package__
sozinha não é suficiente. Você precisará importar pelo menos N pacotes anteriores na hierarquia do módulo, em que N é o número de diretórios-pai (relativos ao diretório do script) que serão pesquisados pelo módulo que está sendo importado.
Portanto,
Adicione o diretório pai do enésimo predecessor do módulo atual aosys.path
Remova o diretório do arquivo atual de sys.path
Importe o módulo pai do módulo atual usando seu nome completo
Defina __package__
com o nome completo de 2
Executar a importação relativa
Vou emprestar arquivos da Solução 1 e adicionar mais alguns subpacotes:
package
├── __init__.py
├── module.py
└── subpackage
├── __init__.py
└── subsubpackage
├── __init__.py
└── standalone.py
Dessa vez, o standalone.py importará o module.py do pacote do pacote usando a seguinte importação relativa
from ... import module # N = 3
Precisamos preceder essa linha com o código padrão, para fazê-la funcionar.
import sys
from pathlib import Path
if __name__ == '__main__' and __package__ is None:
file = Path(__file__).resolve()
parent, top = file.parent, file.parents[3]
sys.path.append(str(top))
try:
sys.path.remove(str(parent))
except ValueError: # Already removed
pass
import package.subpackage.subsubpackage
__package__ = 'package.subpackage.subsubpackage'
from ... import module # N = 3
Ele nos permite executar standalone.py por nome de arquivo:
vaultah@base:~$ python3 package/subpackage/subsubpackage/standalone.py
Running /home/vaultah/package/subpackage/subsubpackage/standalone.py
Importing /home/vaultah/package/__init__.py
Importing /home/vaultah/package/subpackage/__init__.py
Importing /home/vaultah/package/subpackage/subsubpackage/__init__.py
Importing /home/vaultah/package/module.py
Uma solução mais geral envolvida em uma função pode ser encontrada aqui . Exemplo de uso:
if __name__ == '__main__' and __package__ is None:
import_parents(level=3) # N = 3
from ... import module
from ...module.submodule import thing
Solução 3: use ferramentas e importações absolutas
Os passos são -
Substituir importações relativas explícitas por importações absolutas equivalentes
Instale package
para torná-lo importável
Por exemplo, a estrutura de diretórios pode ser a seguinte
.
├── project
│ ├── package
│ │ ├── __init__.py
│ │ ├── module.py
│ │ └── standalone.py
│ └── setup.py
onde setup.py é
from setuptools import setup, find_packages
setup(
name = 'your_package_name',
packages = find_packages(),
)
O restante dos arquivos foi emprestado da Solução 1 .
A instalação permitirá que você importe o pacote independentemente do seu diretório de trabalho (assumindo que não haverá problemas de nomeação).
Podemos modificar o standalone.py para usar esta vantagem (etapa 1):
from package import module # absolute import
Altere seu diretório de trabalho para project
e execute /path/to/python/interpreter setup.py install --user
( --user
instala o pacote no diretório de pacotes do site ) (etapa 2):
vaultah@base:~$ cd project
vaultah@base:~/project$ python3 setup.py install --user
Vamos verificar se agora é possível executar standalone.py como um script:
vaultah@base:~/project$ python3 -i package/standalone.py
Running /home/vaultah/project/package/standalone.py
Importing /home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/__init__.py
Importing /home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/module.py
>>> module
<module 'package.module' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/module.py'>
>>> import sys
>>> sys.modules['package']
<module 'package' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/__init__.py'>
>>> sys.modules['package.module']
<module 'package.module' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/module.py'>
Nota : Se você decidir seguir esse caminho, seria melhor usar ambientes virtuais para instalar pacotes isoladamente.
Solução 4: use importações absolutas e algum código padrão
Francamente, a instalação não é necessária - você pode adicionar algum código padrão ao seu script para fazer com que as importações absolutas funcionem.
Vou pegar emprestados arquivos da Solução 1 e alterar standalone.py :
Adicionar o diretório pai do pacote para sys.path
antes de tentar importar qualquer coisa de pacote usando importações absolutos:
import sys
from pathlib import Path # if you haven't already done so
file = Path(__file__).resolve()
parent, root = file.parent, file.parents[1]
sys.path.append(str(root))
# Additionally remove the current file's directory from sys.path
try:
sys.path.remove(str(parent))
except ValueError: # Already removed
pass
Substitua a importação relativa pela importação absoluta:
from package import module # absolute import
standalone.py é executado sem problemas:
vaultah@base:~$ python3 -i package/standalone.py
Running /home/vaultah/package/standalone.py
Importing /home/vaultah/package/__init__.py
Importing /home/vaultah/package/module.py
>>> module
<module 'package.module' from '/home/vaultah/package/module.py'>
>>> import sys
>>> sys.modules['package']
<module 'package' from '/home/vaultah/package/__init__.py'>
>>> sys.modules['package.module']
<module 'package.module' from '/home/vaultah/package/module.py'>
Sinto que devo avisá-lo: tente não fazer isso, principalmente se o seu projeto tiver uma estrutura complexa.
Como observação lateral, o PEP 8 recomenda o uso de importações absolutas, mas afirma que, em alguns cenários, importações relativas explícitas são aceitáveis:
As importações absolutas são recomendadas, pois geralmente são mais legíveis e tendem a se comportar melhor (ou pelo menos fornecer melhores mensagens de erro). [...] No entanto, as importações relativas explícitas são uma alternativa aceitável às importações absolutas, especialmente quando se trata de layouts complexos de pacotes nos quais o uso de importações absolutas seria desnecessariamente detalhado.