Python: importar módulo de outro diretório no mesmo nível na hierarquia do projeto

87

Já vi todos os tipos de exemplos e outras perguntas semelhantes, mas não consigo encontrar um exemplo que corresponda exatamente ao meu cenário. Eu me sinto um idiota ao fazer isso porque há tantas perguntas semelhantes, mas simplesmente não consigo fazer isso funcionar "corretamente". Aqui está meu projeto:

user_management  (package)
        |
        |------- __init__.py
        |
        |------- Modules/
        |           |
        |           |----- __init__.py
        |           |----- LDAPManager.py
        |           |----- PasswordManager.py
        |
        |------- Scripts/
        |           |
        |           |----- __init__.py
        |           |----- CreateUser.py
        |           |----- FindUser.py

Se eu mover "CreateUser.py" para o diretório user_management principal, posso usar facilmente: "import Modules.LDAPManager"para importar LDAPManager.py --- isso funciona. O que não posso fazer (o que quero fazer) é manter CreateUser.py na subpasta Scripts e importar LDAPManager.py. Eu esperava conseguir isso usando "import user_management.Modules.LDAPManager.py". Isso não funciona. Resumindo, posso fazer com que os arquivos Python olhem mais profundamente na hierarquia, mas não consigo fazer com que um script Python faça referência a um diretório e depois a outro.

Observe que posso resolver meu problema usando:

sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
import Modules.LDAPManager as LDAPManager

Ouvi dizer que isso é uma prática ruim e desanimador.

Os arquivos em Scripts devem ser executados diretamente (o init .py em Scripts é mesmo necessário?). Eu li que, neste caso, devo executar CreateUser.py com o sinalizador -m. Eu tentei algumas variações disso e simplesmente não consigo fazer o CreateUser.py reconhecer LDAPManager.py.

CptSupermrkt
fonte

Respostas:

66

Se eu mover CreateUser.pypara o diretório principal user_management, posso facilmente usar: import Modules.LDAPManagerpara importar LDAPManager.py --- isso funciona.

Por favor, não . Desta forma, o LDAPManagermódulo usado por CreateUserirá não ser o mesmo que o importado via outras importações. Isso pode criar problemas quando você tem algum estado global no módulo ou durante a decapagem / retirada da colheita. Evite importações que funcionam apenas porque o módulo está no mesmo diretório.

Quando você tem uma estrutura de pacote, você deve:

  • Use importações relativas, ou seja, se CreateUser.pyestiver em Scripts/:

     from ..Modules import LDAPManager
    

    Note-se que este era (note o passado tensa) desencorajado pelo PEP 8 só porque versões antigas do python não apoiá-los muito bem, mas este problema foi resolvido anos atrás. A atual versão do PEP 8 não sugeri-los como uma alternativa aceitável para as importações absolutos. Na verdade, gosto deles dentro de pacotes.

  • Use importações absolutas usando o nome completo do pacote ( CreateUser.pyin Scripts/):

     from user_management.Modules import LDAPManager
    

Para que o segundo funcione, o pacote user_managementdeve ser instalado dentro do PYTHONPATH. Durante o desenvolvimento, você pode configurar o IDE para que isso aconteça, sem ter que adicionar chamadas manualmente para sys.path.appendqualquer lugar.

Também acho estranho que Scripts/seja um subpacote. Porque em uma instalação real, o user_managementmódulo seria instalado sob o site-packagesencontrado no lib/diretório (qualquer diretório é usado para instalar bibliotecas em seu sistema operacional), enquanto os scripts devem ser instalados em um bin/diretório (o que contiver executáveis ​​para seu sistema operacional).

Na verdade, acredito Script/que nem deveria estar abaixo user_management. Deve estar no mesmo nível de user_management. Desta forma, você não precisa usar -m, mas simplesmente certificar-se de que o pacote pode ser encontrado (novamente, é uma questão de configurar o IDE, instalar o pacote corretamente ou usar PYTHONPATH=. python Scripts/CreateUser.pypara iniciar os scripts com o caminho correto).


Em resumo, a hierarquia que eu usaria é:

user_management  (package)
        |
        |------- __init__.py
        |
        |------- Modules/
        |           |
        |           |----- __init__.py
        |           |----- LDAPManager.py
        |           |----- PasswordManager.py
        |

 Scripts/  (*not* a package)
        |  
        |----- CreateUser.py
        |----- FindUser.py

Em seguida, o código de CreateUser.pye FindUser.pydeve usar importações absolutas para importar os módulos:

from user_management.Modules import LDAPManager

Durante a instalação, certifique-se de que user_managementtermina em algum lugar no PYTHONPATHe os scripts dentro do diretório para executáveis, de modo que eles possam localizar os módulos. Durante o desenvolvimento, você depende da configuração do IDE ou inicia CreateUser.pyadicionando o Scripts/diretório pai ao PYTHONPATH(quero dizer, o diretório que contém tanto user_managemente Scripts):

PYTHONPATH=/the/parent/directory python Scripts/CreateUser.py

Ou você pode modificar o PYTHONPATHglobalmente para que não precise especificar isso todas as vezes. Em sistemas operacionais Unix (Linux, Mac OS X etc.), você pode modificar um dos scripts de shell para definir a PYTHONPATHvariável externa, no Windows você deve alterar as configurações das variáveis ​​ambientais.


Adendo , acredito, se você estiver usando python2, é melhor evitar as importações relativas implícitas, colocando:

from __future__ import absolute_import

na parte superior de seus módulos. Desta forma import X sempre significa para importar o nível superior do módulo Xe nunca vai tentar importar o X.pyarquivo que está no mesmo diretório (se esse diretório não está no PYTHONPATH). Dessa forma, a única maneira de fazer uma importação relativa é usar a sintaxe explícita (o from . import X), que é melhor ( explícita é melhor do que implícita ).

Isso garantirá que você nunca use as importações relativas implícitas "falsas", uma vez que elas gerariam uma ImportErrorsinalização clara de que algo está errado. Caso contrário, você pode usar um módulo que não seja o que você pensa que é.

Bakuriu
fonte
Se você estiver usando importações relativas, deve executarpython -m user_management.Scripts.CreateUser
mononoke
14

Do Python 2.5 em diante, você pode usar

from ..Modules import LDAPManager

O período inicial leva você "para cima" um nível em sua hierarquia.

Consulte a documentação do Python sobre referências dentro do pacote para importações.

Jonrsharpe
fonte
3

Na "raiz" __init__.pyvocê também pode fazer um

import sys
sys.path.insert(1, '.')

que deve tornar ambos os módulos importáveis.

rdodev
fonte