Tentei ler as perguntas sobre importações de irmãos e até a documentação do pacote , mas ainda não encontrei uma resposta.
Com a seguinte estrutura:
├── LICENSE.md
├── README.md
├── api
│ ├── __init__.py
│ ├── api.py
│ └── api_key.py
├── examples
│ ├── __init__.py
│ ├── example_one.py
│ └── example_two.py
└── tests
│ ├── __init__.py
│ └── test_one.py
Como podem os scripts no examples
e tests
diretórios importar do
api
módulo e ser executado a partir da linha de comando?
Além disso, eu gostaria de evitar o feio sys.path.insert
hack para cada arquivo. Certamente isso pode ser feito em Python, certo?
python
packages
python-import
siblings
zachwill
fonte
fonte
sys.path
hacks e ler a única solução real publicada até agora (após 7 anos!).Respostas:
Sete anos depois
Desde que escrevi a resposta abaixo, modificar
sys.path
ainda é um truque rápido e sujo que funciona bem para scripts privados, mas houve várias melhoriassetup.cfg
para armazenar os metadados)-m
sinalizador e executar como um pacote também funciona (mas ficará um pouco estranho se você quiser converter seu diretório de trabalho em um pacote instalável).sys.path
hacks para vocêEntão, realmente depende do que você quer fazer. Porém, no seu caso, já que parece que seu objetivo é criar um pacote adequado em algum momento, a instalação através do
pip -e
provavelmente é sua melhor aposta, mesmo que ainda não seja perfeita.Resposta antiga
Como já foi dito em outro lugar, a terrível verdade é que você precisa fazer hacks feios para permitir a importação de módulos irmãos ou de pacotes pais de um
__main__
módulo. A questão está detalhada no PEP 366 . A PEP 3122 tentou lidar com as importações de uma maneira mais racional, mas Guido a rejeitou na conta de( aqui )
Porém, eu uso esse padrão regularmente com
Aqui
path[0]
está a pasta pai do script em execução edir(path[0])
a pasta de nível superior.Porém, ainda não consegui usar importações relativas com isso, mas ele permite importações absolutas do nível superior (na
api
pasta pai do seu exemplo ).fonte
-m
forma ou se você instalar o pacote (pip e virtualenv tornar mais fácil)__package__ = "examples"
mim. Por quê você usa isso? 2. Em que situação é,__name__ == "__main__"
mas__package__
não éNone
?__packages__
ajuda se você quiser um caminho absoluto, como oexamples.api
iirc (mas já faz muito tempo desde a última vez que fiz isso) e verificar se o pacote não é Nenhum.Cansado de hacks sys.path?
Existem muitos
sys.path.append
truques disponíveis, mas eu encontrei uma maneira alternativa de resolver o problema em questão.Resumo
packaged_stuff
)setup.py
script create onde você usa setuptools.setup () .pip install -e <myproject_folder>
from packaged_stuff.modulename import function_name
Configuração
O ponto de partida é a estrutura de arquivos que você forneceu, agrupada em uma pasta chamada
myproject
.Vou chamar a
.
pasta raiz e, no meu exemplo, está localizada emC:\tmp\test_imports\
.api.py
Como caso de teste, vamos usar o seguinte ./api/api.py
test_one.py
Tente executar test_one:
Também tentar importações relativas não funcionará:
O uso
from ..api.api import function_from_api
resultaria emPassos
O conteúdo para o
setup.py
seria *Se você estiver familiarizado com ambientes virtuais, ative um e pule para a próxima etapa. O uso de ambientes virtuais não é absolutamente necessário, mas eles realmente o ajudarão a longo prazo (quando você tiver mais de um projeto em andamento ..). As etapas mais básicas são (executadas na pasta raiz)
python -m venv venv
source ./venv/bin/activate
(Linux, macOS) ou./venv/Scripts/activate
(Win)Para saber mais sobre isso, basta pesquisar no "python virtual env tutorial" ou similar. Você provavelmente nunca precisará de outros comandos além de criar, ativar e desativar.
Depois de criar e ativar um ambiente virtual, seu console deve dar o nome do ambiente virtual entre parênteses
e sua árvore de pastas deve ficar assim **
Instale seu pacote de nível superior
myproject
usandopip
. O truque é usar a-e
bandeira ao fazer a instalação. Dessa forma, ele é instalado em um estado editável e todas as edições feitas nos arquivos .py serão automaticamente incluídas no pacote instalado.No diretório raiz, execute
pip install -e .
(observe o ponto, significa "diretório atual")Você também pode ver que ele está instalado usando
pip freeze
myproject.
em suas importaçõesObserve que você precisará adicionar
myproject.
apenas importações que não funcionariam de outra maneira. As importações que funcionaram sem osetup.py
&pip install
ainda funcionarão bem. Veja um exemplo abaixo.Teste a solução
Agora, vamos testar a solução usando
api.py
definido acima etest_one.py
definido abaixo.test_one.py
executando o teste
* Consulte os documentos setuptools para obter mais exemplos detalhados de setup.py.
** Na realidade, você pode colocar seu ambiente virtual em qualquer lugar do seu disco rígido.
fonte
-e git+https://[email protected]/folder/myproject.git@f65466656XXXXX#egg=myproject
Alguma idéia de como resolver?ModuleNotFoundError
? Instalei 'myproject' em um virtualenv seguindo estas etapas e, quando entro em uma sessão interpretada e executoimport myproject
, receboModuleNotFoundError: No module named 'myproject'
?pip list installed | grep myproject
mostra que ele está lá, o diretório está correto e a verificaçãopip
e apython
verificação estão corretas.pip list
pacotes de shows, enquantopip freeze
mostra nomes estranhos se instalado com a bandeira -eAqui está outra alternativa que eu insiro na parte superior dos arquivos Python na
tests
pasta:fonte
..
aqui é relativo ao diretório do qual você está executando - não ao diretório que contém esse arquivo de teste / exemplo. Estou executando a partir do diretório do projeto e, em./
vez disso, precisava . Espero que isso ajude outra pessoa.sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
@JoshuaDetwilerVocê não precisa e não deve invadir, a
sys.path
menos que seja necessário e, nesse caso, não é. Usar:Executado a partir do diretório do projeto:
python -m tests.test_one
.Você provavelmente deve se mover
tests
(se forem unittests da API) para dentroapi
e executarpython -m api.test
para executar todos os testes (supondo que exista__main__.py
) oupython -m api.test.test_one
executartest_one
.Você também pode remover
__init__.py
deexamples
(não é um pacote Python) e executar os exemplos em um virtualenv ondeapi
está instalado, por exemplo,pip install -e .
em um virtualenv instalaria oapi
pacote local se você tiver o apropriadosetup.py
.fonte
python -m api.test.test_one
de qualquer lugar quando o virtualenv estiver ativado. Se você não pode configurar o PyCharm para executar seus testes, tente fazer uma nova pergunta de Estouro de Pilha (se você não encontrar uma pergunta existente sobre este tópico).Ainda não tenho a compreensão de Pythonology necessária para ver a maneira pretendida de compartilhar código entre projetos não relacionados sem um hack de importação relativo / irmão. Até aquele dia, esta é a minha solução. Para
examples
outests
importar coisas de..\api
, seria semelhante a:fonte
Para importações de pacotes irmãos, você pode usar o método insert ou append do módulo [sys.path] [2] :
Isso funcionará se você estiver iniciando seus scripts da seguinte maneira:
Por outro lado, você também pode usar a importação relativa:
Nesse caso, você terá que iniciar seu script com o argumento '-m' (observe que, nesse caso, você não deve dar o '.py' extensão ):
Obviamente, você pode combinar as duas abordagens, para que seu script funcione, independentemente de como é chamado:
fonte
__file__
mundial, então eu tive que usar o seguinte:sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0]))))
Mas ele funciona em qualquer diretório agoraTLDR
Esse método não requer ferramentas de instalação, invasões de caminho, argumentos adicionais da linha de comando ou especificação do nível superior do pacote em todos os arquivos do seu projeto.
Basta criar um script no diretório pai do que você está chamando para ser o seu
__main__
e executar tudo a partir daí. Para mais explicações, continue lendo.Explicação
Isso pode ser feito sem hackear um novo caminho juntos, argumentos extras na linha de comando ou adicionar código a cada um dos seus programas para reconhecer seus irmãos.
A razão pela qual isso falha, como acredito que foi mencionado antes, é que os programas que estão sendo chamados têm o seu
__name__
conjunto como__main__
. Quando isso ocorre, o script que está sendo chamado se aceita no nível superior do pacote e se recusa a reconhecer scripts nos diretórios irmãos.No entanto, tudo no nível superior do diretório ainda reconhecerá QUALQUER OUTRA COISA no nível superior. Isso significa que a única coisa que você deve fazer para obter arquivos nos diretórios irmãos para se reconhecer / utilizar um ao outro é chamá-los a partir de um script no diretório pai.
Prova de conceito Em um diretório com a seguinte estrutura:
Main.py
contém o seguinte código:sib1 / call.py contém:
e sib2 / callsib.py contém:
Se você reproduzir este exemplo, notará que a chamada
Main.py
resultará na impressão de "Recebido", conforme definido em,sib2/callsib.py
mesmo quesib2/callsib.py
recebidosib1/call.py
. No entanto, se alguém ligar diretamentesib1/call.py
(após fazer as alterações apropriadas nas importações), isso gera uma exceção. Embora tenha funcionado quando chamado pelo script em seu diretório pai, ele não funcionará se acreditar que está no nível superior do pacote.fonte
Eu fiz um projeto de amostra para demonstrar como eu lidei com isso, que é de fato outro hack do sys.path, conforme indicado acima. Exemplo de importação de irmão do Python , que se baseia em:
if __name__ == '__main__': import os import sys sys.path.append(os.getcwd())
Isso parece ser bastante eficaz desde que seu diretório de trabalho permaneça na raiz do projeto Python. Se alguém implantar isso em um ambiente de produção real, seria ótimo saber se funciona lá também.
fonte
Você precisa ver como as instruções de importação são gravadas no código relacionado. Se
examples/example_one.py
usa a seguinte instrução de importação:... espera que o diretório raiz do projeto esteja no caminho do sistema.
A maneira mais fácil de dar suporte a isso sem hacks (como você disse) seria executar os exemplos no diretório de nível superior, como este:
fonte
$ python examples/example.py Traceback (most recent call last): File "examples/example.py", line 3, in <module> from api.api import API ImportError: No module named api.api
. Eu também recebo o mesmoimport api.api
.Caso alguém usando Pydev no Eclipse acabe aqui: você pode adicionar o caminho pai do irmão (e, portanto, o pai do módulo de chamada) como uma pasta de biblioteca externa usando Projeto-> Propriedades e configurando Bibliotecas Externas no menu esquerdo Pydev-PYTHONPATH . Então você pode importar do seu irmão, por exemplo
from sibling import some_class
.fonte
Primeiro, você deve evitar ter arquivos com o mesmo nome que o próprio módulo. Pode quebrar outras importações.
Quando você importa um arquivo, primeiro o intérprete verifica o diretório atual e depois pesquisa os diretórios globais.
Dentro
examples
outests
você pode ligar para:fonte
Traceback (most recent call last): File "example_one.py", line 3, in <module> from ..api import api ValueError: Attempted relative import in non-package
__init__.py
arquivo ao diretório de nível superior. Caso contrário Python não pode tratá-lo como um módulo__name__
é em__main__
vez depackage.module
, o Python não pode ver seu pacote pai, portanto,.
nada indica.