Python: importar um subpacote ou submódulo

90

Como já usei pacotes simples, não esperava o problema que encontrei com pacotes aninhados. Aqui está…

Layout de diretório

dir
 |
 +-- test.py
 |
 +-- package
      |
      +-- __init__.py
      |
      +-- subpackage
           |
           +-- __init__.py
           |
           +-- module.py

Conteúdo de init .py

Ambos package/__init__.pye package/subpackage/__init__.pyestão vazios.

Conteúdo de module.py

# file `package/subpackage/module.py`
attribute1 = "value 1"
attribute2 = "value 2"
attribute3 = "value 3"
# and as many more as you want...

Conteúdo de test.py(3 versões)

Versão 1

# file test.py
from package.subpackage.module import *
print attribute1 # OK

Essa é a maneira ruim e insegura de importar coisas (importar tudo em massa), mas funciona.

Versão 2

# file test.py
import package.subpackage.module
from package.subpackage import module # Alternative
from module import attribute1

Uma maneira mais segura de importar, item por item, mas falha, o Python não quer isso: falha com a mensagem: "Nenhum módulo denominado módulo". Contudo …

# file test.py
import package.subpackage.module
from package.subpackage import module # Alternative
print module # Surprise here

… Diz <module 'package.subpackage.module' from '...'>. Então isso é um módulo, mas não é um módulo / -P 8-O ... uh

Versão 3

# file test.py v3
from package.subpackage.module import attribute1
print attribute1 # OK

Este funciona. Então você é forçado a usar o prefixo exagerado o tempo todo ou usar a forma insegura como na versão 1 e não permitida pelo Python para usar a forma prática e segura? A melhor maneira, que é segura e evita o prefixo longo desnecessário, é a única que o Python rejeita? É porque ama import *ou porque ama prefixos longos demais (o que não ajuda a reforçar essa prática)?

Desculpe pelas palavras duras, mas já faz dois dias que estou tentando contornar esse comportamento estúpido. A menos que eu estivesse totalmente errado em algum lugar, isso me deixará com a sensação de que algo está realmente quebrado no modelo de pacote e subpacotes do Python.

Notas

  • Não quero depender de sys.path, para evitar efeitos colaterais globais, nem de *.ptharquivos, que são apenas outra forma de brincar sys.pathcom os mesmos efeitos globais. Para que a solução seja limpa, ela deve ser apenas local. Ou o Python é capaz de lidar com subpacotes, ou não, mas não deveria ser necessário brincar com a configuração global para poder lidar com coisas locais.
  • Também tentei usar importações no package/subpackage/__init__.py, mas não resolveu nada, faz o mesmo, e reclama que subpackagenão é um módulo conhecido, enquanto print subpackagediz que é um módulo (comportamento estranho, de novo).

Pode ser que eu esteja totalmente errado (a opção que eu preferiria), mas isso me deixa muito desapontado com o Python.

Alguma outra forma conhecida além das três que experimentei? Algo que eu não sei?

(suspiro)

-----% <----- editar ----->% -----

Conclusão até agora (após comentários das pessoas)

Não há nada como um subpacote real em Python, já que todas as referências de pacote vão para um dicionário global, apenas, o que significa que não há dicionário local, o que significa que não há maneira de gerenciar a referência de pacote local.

Você deve usar prefixo completo ou prefixo curto ou alias. Como em:

Versão de prefixo completo

from package.subpackage.module import attribute1
# An repeat it again an again
# But after that, you can simply:
use_of (attribute1)

Versão de prefixo curto (mas prefixo repetido)

from package.subpackage import module
# Short but then you have to do:
use_of (module.attribute1)
# and repeat the prefix at every use place

Ou então, uma variação do anterior.

from package.subpackage import module as m
use_of (m.attribute1)
# `m` is a shorter prefix, but you could as well
# define a more meaningful name after the context

Versão fatorada

Se você não se importa em importar várias entidades de uma vez em um lote, pode:

from package.subpackage.module import attribute1, attribute2
# and etc.

Não está no meu primeiro gosto favorito (prefiro ter uma declaração de importação por entidade importada), mas pode ser aquela que eu pessoalmente prefiro.

Atualização (14/09/2012):

Finalmente parece estar bem na prática, exceto com um comentário sobre o layout. Em vez do acima, usei:

from package.subpackage.module import (

    attribute1, 
    attribute2,
    attribute3,
    ...)  # and etc.
Hibou57
fonte
Como vão as coisas quando você escreve "from. Import module" em "/package/subpackage/__init__.py"?
Markus Unterwaditzer 01 de
Sua "versão fatorada" parece exatamente a certa para o que você deseja fazer. Se você fizer uma linha de importação separada para attribute1 e attribute2 (como você "prefere"), estará apenas se dando mais trabalho deliberadamente. Não há razão para fazer isso.
BrenBarn 01 de
Desculpe, mas não consigo o que você deseja. Você poderia reformular sua pergunta de uma maneira mais clara? O que você gostaria de fazer exatamente? Quero dizer, o que você gostaria de escrever que não funciona e como você espera que funcione? Pelo que li, acho que você qual a semântica da importação para ser semelhante à do Java ou talvez do C inclua. Última coisa: você pode tornar um módulo "importação estrela" seguro adicionando uma __all__variável que contém uma lista dos nomes que devem ser exportados quando importada estrela. editar: Ok, lendo a resposta de BrenBarn eu entendi o que você quis dizer.
Bakuriu 01 de

Respostas:

68

Você parece não ter entendido como importprocura por módulos. Quando você usa uma instrução de importação, ela sempre pesquisa o caminho real do módulo (e / ou sys.modules); ele não faz uso de objetos de módulo no namespace local que existem por causa de importações anteriores. Quando você faz:

import package.subpackage.module
from package.subpackage import module
from module import attribute1

A segunda linha procura um pacote chamado package.subpackagee importa moduledesse pacote. Esta linha não tem efeito na terceira linha. A terceira linha apenas procura um módulo chamado modulee não encontra nenhum. Ele não "reutiliza" o objeto chamado moduleque você obteve na linha acima.

Em outras palavras from someModule import ..., não significa "do módulo denominado someModule que importei anteriormente ..." significa "do módulo denominado someModule que você encontra em sys.path ...". Não há como construir "incrementalmente" o caminho de um módulo importando os pacotes que levam a ele. Você sempre deve referir-se ao nome completo do módulo ao importar.

Não está claro o que você está tentando alcançar. Se você deseja apenas importar o objeto particular attribute1, basta fazer from package.subpackage.module import attribute1e pronto. Você nunca precisa se preocupar com o longo package.subpackage.moduledepois de importar o nome que deseja dele.

Se você não quiser ter acesso ao módulo de acesso outros nomes mais tarde, então você pode fazer from package.subpackage import modulee, como você viu que você pode então fazer module.attribute1e assim por diante, tanto quanto você gosta.

Se você deseja ambos - isto é, se deseja ter attribute1acesso direto e deseja moduleacessá-lo, basta fazer as duas coisas acima:

from package.subpackage import module
from package.subpackage.module import attribute1
attribute1 # works
module.someOtherAttribute # also works

Se você não gosta de digitar package.subpackagenem mesmo duas vezes, pode apenas criar manualmente uma referência local para attribute1:

from package.subpackage import module
attribute1 = module.attribute1
attribute1 # works
module.someOtherAttribute #also works
BrenBarn
fonte
Seus comentários vão na mesma direção que os de Ignacio Vazquez-Abrams (comentei sua mensagem). Sua adição no final, sobre usar module.attribute1é algo que eu pensei, mas eu pensei que haveria uma maneira de evitar a necessidade de um prefixo em todos os lugares. Portanto, tenho que usar um prefixo em todos os lugares ou criar um alias local, repetindo o nome. Não é o estilo que eu esperava, mas se não tem jeito (afinal, estou acostumado com Ada, que exige algo parecido com suas declarações de renomeação).
Hibou57 01 de
@ Hibou57: Ainda não está claro para mim o que você está tentando realizar na sua "Versão 2". O que você quer fazer que não seja possível? Você deseja nunca redigitar qualquer parte do nome do pacote / módulo / atributo, mas ainda importar o módulo e seu atributo?
BrenBarn 01 de
Eu queria ter uma referência de pacote local, da mesma forma que você pode ter uma referência de objeto local. Parece que finalmente há referências de módulo realmente locais, mas você não pode importar deles. É uma mistura de local e global com um sabor engraçado (alguns produtos podem ser locais, outros devem ser globais, não gosto, mas estou bem, contanto que agora entenda melhor como funciona). A propósito, obrigado pela sua mensagem.
Hibou57 01 de
1
Não tenho certeza se você ainda entende como funciona. Ou que você fez em 2012 em qualquer caso.
Hejazzman
1
Sempre que volto para Python após uma dispensa de 6 meses, acabo aqui. Se eu pudesse votar positivamente cada vez que visito esta página! Vou fazer um pôster gigantesco com esta frase: "Não há como" incrementar "construir o caminho de um módulo importando os pacotes que levam a ele."
PatrickT
10

O motivo da falha # 2 é porque sys.modules['module']não existe (a rotina de importação tem seu próprio escopo e não pode ver o modulenome local) e não há modulemódulo ou pacote no disco. Observe que você pode separar vários nomes importados por vírgulas.

from package.subpackage.module import attribute1, attribute2, attribute3

Além disso:

from package.subpackage import module
print module.attribute1
Ignacio Vazquez-Abrams
fonte
Sua referência sys.modules['name']que eu não sabia até agora, me fez pensar que eu estava com medo (e BrenBarn confirma): não há nada como subpacotes reais em Python. sys.modules, como seu nome sugere, é global e, se todas as referências a módulos dependem disso, então não há nada como uma referência local a um módulo (pode vir com o Python 3.x?).
Hibou57 01 de
Seu uso de "referência" é ambíguo; o primeiro importem # 2 gera uma referência local para package.subpackage.modulevincular module.
Ignacio Vazquez-Abrams
Sim, mas esse é um “módulo” do qual não posso importar ;-)
Hibou57 01 de
0

Se tudo o que você está tentando fazer é obter attribute1 em seu namespace global, a versão 3 parece adequada. Por que é um prefixo de exagero?

Na versão 2, em vez de

from module import attribute1

você pode fazer

attribute1 = module.attribute1
Thomas Vander Stichele
fonte
attribute1 = module.attribute1está apenas repetindo o nome sem nenhum valor agregado. Sei que funciona, mas não gosto desse estilo (o que não significa que não goste da sua resposta).
Hibou57 01 de
2
Acho que, como todas as pessoas comentando aqui, não entendo o que você quer fazer. Em todos os exemplos que você dá, parece que você deseja terminar com um símbolo de um subpacote em seu namespace. Seu exemplo não funcional (exemplo 2) deseja fazê-lo importando um submódulo de um pacote e, em seguida, importando um símbolo desse submódulo. Não sei por que você quer fazer isso em duas etapas em vez de uma. Talvez explique mais qual seria sua solução ideal e por quê.
Thomas Vander Stichele