Como importar uma classe Python que está em um diretório acima?

212

Eu quero herdar de uma classe em um arquivo que se encontra em um diretório acima do atual.

É possível importar relativamente esse arquivo?

user129975
fonte

Respostas:

177

from ..subpkg2 import mod

De acordo com os documentos do Python: Quando estiver dentro de uma hierarquia de pacotes, use dois pontos, como diz o documento da instrução de importação :

Ao especificar qual módulo importar, você não precisa especificar o nome absoluto do módulo. Quando um módulo ou pacote está contido em outro pacote, é possível fazer uma importação relativa no mesmo pacote superior sem precisar mencionar o nome do pacote. Usando pontos iniciais no módulo ou pacote fromespecificado, você pode especificar a altura de percorrer a hierarquia atual do pacote sem especificar nomes exatos. Um ponto inicial significa o pacote atual em que o módulo que está fazendo a importação existe. Dois pontos significam um nível de pacote . Três pontos sobem dois níveis, etc. Portanto, se você executar a from . import modpartir de um módulo no pkgpacote, você terminará importando pkg.mod. Se você executar from ..subpkg2 import modde dentro, pkg.subpkg1você importarápkg.subpkg2.mod. A especificação para importações relativas está contida no PEP 328 .

O PEP 328 trata de importações absolutas / relativas.

gimel
fonte
4
up1 = os.path.abspath ('..') sys.path.insert (0, up1)
rp.
O Pep 328 mostra apenas a versão Python: 2.4, 2,5, 2,6. A versão 3 é deixada para almas com mais conhecimento.
gimel
22
Este erro gatilhos ValueError: tentativa de importação em relação além pacote de nível superior
Carlo
4
Resultados no ImportError: tentativa de importação relativa sem pacote pai conhecido
Rotkiv
116
import sys
sys.path.append("..") # Adds higher directory to python modules path.
Sepero
fonte
1
isso funciona para mim. Depois de adicionar isso, posso importar diretamente os módulos pai, sem precisar usar "..".
Evan Hu
8
isso só funciona, grosso modo, se o valor PWD do aplicativo - seu diretório atual, for filho do pai. Portanto, mesmo que funcione, muitos tipos de mudanças sistêmicas podem desalojá-lo.
Phlip
Isso funcionou para mim também na importação de módulos de nível superior. Eu uso isso com os.chdir ("..") para carregar outros arquivos de nível superior.
Surendra Shrestha
Isso parece desencadear o mesmo erro da resposta acima do gimel.
Carlo
81

A resposta do @ gimel está correta se você pode garantir a hierarquia de pacotes que ele menciona. Se você não pode - se sua necessidade real é a que você expressou, exclusivamente vinculada a diretórios e sem qualquer relação necessária com o empacotamento -, você precisa trabalhar __file__para descobrir o diretório pai (algumas os.path.dirnamechamadas serão necessárias; -), então (se esse diretório não estiver ligado sys.path) preceder temporariamente inserção disse dir no início de sys.path, __import__, retire disse dir novamente - o trabalho sujo de fato, mas, "quando você deve, você deve" (e se esforça para Pyhon nunca impeça o programador de fazer o que deve ser feito - assim como o padrão ISO C diz na seção "Spirit of C" em seu prefácio! -).

Aqui está um exemplo que pode funcionar para você:

import sys
import os.path
sys.path.append(
    os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir)))

import module_in_parent_dir
Alex Martelli
fonte
2
isso pode adicionar um diretório dentro de um pacote Python para sys.pathdisponibilizar o mesmo módulo com nomes diferentes e todos os erros correspondentes. autopath.py em pypy ou _preamble.py em twisted resolva-o usando um critério de pesquisa que identifica o pacote de nível superior enquanto percorre os diretórios para cima.
JFS
4
Você pode fazer algo como sys.path.remove(pathYouJustAdded)após a importação necessária para não manter esse novo caminho.
Matthias
35

Importe o módulo de um diretório que esteja exatamente um nível acima do diretório atual:

from .. import module
Sepero
fonte
38
Eu me: tentativa de importação em relação além pacote de nível superior :(
RicardoE
25
using from .. import module I got error ValueError: Tentativa de importação relativa no pacote não seguindo a recomendação
user3428154
4

Como carregar um módulo que é um diretório ativo

prefácio: reescrevi substancialmente uma resposta anterior com a esperança de ajudar as pessoas a entrar no ecossistema do python, e espero dar a todos a melhor mudança de sucesso com o sistema de importação do python.

Isso cobrirá as importações relativas dentro de um pacote , que eu acho que é o caso mais provável da pergunta do OP.

Python é um sistema modular

É por isso que escrevemos import foopara carregar um módulo "foo" no namespace raiz, em vez de escrever:

foo = dict();  # please avoid doing this
with open(os.path.join(os.path.dirname(__file__), '../foo.py') as foo_fh:  # please avoid doing this
    exec(compile(foo_fh.read(), 'foo.py', 'exec'), foo)  # please avoid doing this

Python não está acoplado a um sistema de arquivos

É por isso que podemos incorporar python em ambientes onde não há um sistema de arquivos defacto sem fornecer um sistema virtual, como o Jython.

A dissociação de um sistema de arquivos permite que as importações sejam flexíveis; esse design permite coisas como importações de arquivos compactados / zip, singletons de importação, cache de bytecode, extensões cffi e até carregamento remoto de definição de código.

Portanto, se as importações não estão acopladas a um sistema de arquivos, o que significa "um diretório ativo"? Temos que escolher algumas heurísticas, mas podemos fazer isso, por exemplo, ao trabalhar em um pacote , já foram definidas algumas heurísticas que fazem com que as importações relativas gostem .fooe..foo funcionem dentro do mesmo pacote. Legal!

Se você deseja sinceramente associar seus padrões de carregamento de código-fonte a um sistema de arquivos, você pode fazer isso. Você terá que escolher suas próprias heurísticas, e usar algum tipo de importação de máquinas, eu recomendo importlib

O exemplo importlib do Python se parece com isso:

import importlib.util
import sys

# For illustrative purposes.
file_path = os.path.join(os.path.dirname(__file__), '../foo.py')
module_name = 'foo'

foo_spec = importlib.util.spec_from_file_location(module_name, file_path)
# foo_spec is a ModuleSpec specifying a SourceFileLoader
foo_module = importlib.util.module_from_spec(foo_spec)
sys.modules[module_name] = foo_module
foo_spec.loader.exec_module(foo_module)

foo = sys.modules[module_name]
# foo is the sys.modules['foo'] singleton

Embalagem

Há um ótimo exemplo de projeto disponível oficialmente aqui: https://github.com/pypa/sampleproject

Um pacote python é uma coleção de informações sobre seu código-fonte, que pode informar outras ferramentas sobre como copiar seu código-fonte em outros computadores e como integrar seu código-fonte no caminho do sistema para que import foofuncione em outros computadores (independentemente do intérprete, sistema operacional do host etc.)

Estrutura de Diretórios

Vamos ter um nome de pacote foo, em algum diretório (de preferência um diretório vazio).

some_directory/
    foo.py  # `if __name__ == "__main__":`  lives here

Minha preferência é criar setup.pycomo irmão foo.py, porque isso simplifica a gravação do arquivo setup.py, no entanto, você pode escrever a configuração para alterar / redirecionar tudo o que o setuptools faz por padrão, se desejar; por exemplo, colocar foo.pyem um diretório "src /" é um pouco popular, não abordado aqui.

some_directory/
    foo.py
    setup.py

.

#!/usr/bin/env python3
# setup.py

import setuptools

setuptools.setup(
    name="foo",
    ...
    py_modules=['foo'],
)

.

python3 -m pip install --editable ./  # or path/to/some_directory/

"editável" -e, mais uma vez, redirecionará o mecanismo de importação para carregar os arquivos de origem nesse diretório, em vez de copiar os arquivos exatos atuais para a biblioteca do ambiente de instalação. Isso também pode causar diferenças de comportamento na máquina de um desenvolvedor, não deixe de testar seu código! Existem outras ferramentas além do pip, no entanto, eu recomendo que o pip seja o introdutório :)

Também gosto de criar fooum "pacote" (um diretório que contém __init__.py) em vez de um módulo (um único arquivo ".py"); ambos "pacotes" e "módulos" podem ser carregados no espaço para nome raiz, os módulos permitem espaços para nome aninhados, o que é útil se quisermos importar "um diretório relativo acima".

some_directory/
    foo/
        __init__.py
    setup.py

.

#!/usr/bin/env python3
# setup.py

import setuptools

setuptools.setup(
    name="foo",
    ...
    packages=['foo'],
)

Eu também gosto de fazer a foo/__main__.py, isso permite que o python execute o pacote como um módulo, por exemplo python3 -m foo, execute foo/__main__.pycomo __main__.

some_directory/
    foo/
        __init__.py
        __main__.py  # `if __name__ == "__main__":`  lives here, `def main():` too!
    setup.py

.

#!/usr/bin/env python3
# setup.py

import setuptools

setuptools.setup(
    name="foo",
    ...
    packages=['foo'],
    ...
    entry_points={
        'console_scripts': [
            # "foo" will be added to the installing-environment's text mode shell, eg `bash -c foo`
            'foo=foo.__main__:main',
        ]
    },
)

Permite detalhar isso com mais alguns módulos: Basicamente, você pode ter uma estrutura de diretórios assim:

some_directory/
    bar.py           # `import bar`
    foo/
        __init__.py  # `import foo`
        __main__.py
        baz.py       # `import foo.baz
        spam/           
            __init__.py  # `import foo.spam`
            eggs.py      # `import foo.spam.eggs`
    setup.py

setup.py convencionalmente contém informações de metadados sobre o código-fonte, como:

  • quais dependências são necessárias para instalar com o nome "install_requires"
  • qual nome deve ser usado para o gerenciamento de pacotes (instalar / desinstalar "nome"), sugiro que corresponda ao seu nome de pacote python primário no nosso caso foo, embora seja popular substituir hífens por sublinhados
  • informações de licenciamento
  • tags de maturidade (alfa / beta / etc),
  • tags de público-alvo (para desenvolvedores, para aprendizado de máquina etc.),
  • conteúdo da documentação de uma página (como um README),
  • nomes de shell (nomes que você digita no shell do usuário, como bash, ou nomes encontrados em um shell gráfico do usuário, como um menu Iniciar),
  • uma lista de módulos python que este pacote instalará (e desinstalará)
  • um ponto de entrada padrão "executar testes" python ./setup.py test

É muito expansivo, podendo até compilar extensões em tempo real se um módulo de origem estiver sendo instalado em uma máquina de desenvolvimento. Para um exemplo diário, recomendo o setup.py do repositório de amostras PYPA

Se você estiver liberando um artefato de construção, por exemplo, uma cópia do código destinado a executar computadores quase idênticos, um arquivo requirements.txt é uma maneira popular de capturar informações exatas de dependência, em que "install_requires" é uma boa maneira de capturar o mínimo e versões máximas compatíveis. No entanto, como as máquinas de destino são praticamente idênticas, eu recomendo criar um tarball de um prefixo python inteiro. Isso pode ser complicado, muito detalhado para entrar aqui. Confira pip install's --targetopção, ou virtualenv aka venv para leads.

de volta ao exemplo

como importar um arquivo em um diretório:

Em foo / spam / eggs.py, se quiséssemos código de foo / baz, poderíamos solicitá-lo por seu espaço de nome absoluto:

import foo.baz

Se desejássemos reservar a capacidade de mover eggs.py para outro diretório no futuro com alguma outra bazimplementação relativa , poderíamos usar uma importação relativa como:

import ..baz
ThorSummoner
fonte
-5

Python é um sistema modular

Python não depende de um sistema de arquivos

Para carregar o código python de forma confiável, tenha esse código em um módulo e esse módulo instalado na biblioteca do python.

Os módulos instalados sempre podem ser carregados no namespace de nível superior com import <name>


Há um ótimo projeto de amostra disponível oficialmente aqui: https://github.com/pypa/sampleproject

Basicamente, você pode ter uma estrutura de diretórios assim:

the_foo_project/
    setup.py  

    bar.py           # `import bar`
    foo/
      __init__.py    # `import foo`

      baz.py         # `import foo.baz`

      faz/           # `import foo.faz`
        __init__.py
        daz.py       # `import foo.faz.daz` ... etc.

.

Certifique-se de declarar o seu setuptools.setup()em setup.py,

exemplo oficial: https://github.com/pypa/sampleproject/blob/master/setup.py

No nosso caso, provavelmente queremos exportar bar.pye foo/__init__.py, meu breve exemplo:

setup.py

#!/usr/bin/env python3

import setuptools

setuptools.setup(
    ...
    py_modules=['bar'],
    packages=['foo'],
    ...
    entry_points={}, 
        # Note, any changes to your setup.py, like adding to `packages`, or
        # changing `entry_points` will require the module to be reinstalled;
        # `python3 -m pip install --upgrade --editable ./the_foo_project
)

.

Agora podemos instalar nosso módulo na biblioteca python; com o pip, você pode instalar the_foo_projectna sua biblioteca python no modo de edição, para que possamos trabalhar em tempo real

python3 -m pip install --editable=./the_foo_project

# if you get a permission error, you can always use 
# `pip ... --user` to install in your user python library

.

Agora, a partir de qualquer contexto python, podemos carregar nossos py_modules e pacotes compartilhados

foo_script.py

#!/usr/bin/env python3

import bar
import foo

print(dir(bar))
print(dir(foo))
ThorSummoner
fonte
2
devo mencionar que eu sempre instalo meu módulo enquanto trabalho nele pip install --edit foo, quase sempre dentro de um virtualenv. Quase nunca escrevo um módulo que não se destina a ser instalado. se eu entendi mal algo que gostaria de saber.
ThorSummoner
Também devo mencionar que o uso de um conjunto de testes venv-auto-creation, como o tox, é muito útil, pois editablelinks de ovo e módulos python instalados não são exatamente iguais em todos os aspectos; por exemplo, adicionar um novo espaço para nome a um módulo editável será encontrado na pesquisa de caminho, mas se isso não for exportado no arquivo setup.py, ele não será empacotado / instalado! Teste seu caso de uso :)
ThorSummoner 11/04