Python: importazione di un sottopacchetto o sottomodulo


94

Avendo già utilizzato i pacchetti flat, non mi aspettavo il problema riscontrato con i pacchetti nidificati. Qui è…

Layout delle directory

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

Contenuto di init .py

Entrambi package/__init__.pye package/subpackage/__init__.pysono vuoti.

Il contenuto di module.py

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

Contenuto di test.py(3 versioni)

Versione 1

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

Questo è il modo cattivo e pericoloso di importare le cose (importa tutto in blocco), ma funziona.

Versione 2

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

Un modo più sicuro per importare, elemento per elemento, ma fallisce, Python non vuole questo: fallisce con il messaggio: "Nessun modulo denominato modulo". Però …

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

... dice <module 'package.subpackage.module' from '...'>. Quindi questo è un modulo, ma non è un modulo / -P 8-O ... uh

Versione 3

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

Questo funziona. Quindi sei costretto a usare il prefisso overkill tutto il tempo o usi il modo non sicuro come nella versione # 1 e non sei autorizzato da Python a usare il modo comodo e sicuro? Il modo migliore, che è sicuro ed evita inutili lunghi prefissi è l'unico che Python rifiuta? È perché ama import *o perché ama i prefissi troppo lunghi (il che non aiuta a far rispettare questa pratica) ?.

Scusa per le parole dure, ma sono due giorni che cerco di aggirare questo comportamento stupido. A meno che non mi sia sbagliato completamente da qualche parte, questo mi lascerà con la sensazione che qualcosa sia davvero rotto nel modello di pacchetto e sotto-pacchetti di Python.

Appunti

  • Non voglio fare affidamento su sys.path, per evitare effetti collaterali globali, né sui *.pthfile, che sono solo un altro modo per giocare sys.pathcon gli stessi effetti globali. Affinché la soluzione sia pulita, deve essere solo locale. O Python è in grado di gestire il sottopacchetto, o non lo è, ma non dovrebbe richiedere di giocare con la configurazione globale per essere in grado di gestire cose locali.
  • Ho anche provato a usare le importazioni in package/subpackage/__init__.py, ma non ha risolto nulla, fa lo stesso e si lamenta subpackagenon è un modulo noto, mentre print subpackagedice che è un modulo (comportamento strano, di nuovo).

Può darsi che mi sbagli completamente (l'opzione che preferirei), ma questo mi fa sentire molto deluso da Python.

Qualche altro modo conosciuto oltre ai tre che ho provato? Qualcosa che non so?

(sospiro)

-----% <----- modifica ----->% -----

Conclusione finora (dopo i commenti delle persone)

Non c'è niente come un vero sotto-pacchetto in Python, poiché tutti i riferimenti ai pacchetti vanno solo a un dizionario globale, il che significa che non c'è un dizionario locale, il che implica che non c'è modo di gestire il riferimento al pacchetto locale.

Devi usare il prefisso completo o il prefisso breve o l'alias. Come in:

Versione con prefisso completo

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

Versione con prefisso breve (ma prefisso ripetuto)

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

Oppure, una variazione di quanto sopra.

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

Versione fattorizzata

Se non ti dispiace importare più entità contemporaneamente in un batch, puoi:

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

Non nel mio primo gusto preferito (preferisco avere una dichiarazione di importazione per entità importata), ma potrebbe essere quella che preferirò personalmente.

Aggiornamento (2012-09-14):

Finalmente sembra essere OK in pratica, tranne che con un commento sul layout. Invece di quanto sopra, ho usato:

from package.subpackage.module import (

    attribute1, 
    attribute2,
    attribute3,
    ...)  # and etc.

Come vanno le cose quando scrivi "from. Import module" in "/package/subpackage/__init__.py"?
Markus Unterwaditzer

La tua "versione fattorizzata" sembra esattamente quella giusta per quello che vuoi fare. Se esegui una riga di importazione separata per attributo1 e attributo2 (come "preferisci"), ti stai solo dedicando deliberatamente a più lavoro. Non c'è motivo per farlo.
BrenBarn

Scusa ma non ottengo quello che vuoi. Potresti riformulare la tua domanda in modo più chiaro? Cosa vorresti fare esattamente? Voglio dire, cosa vorresti scrivere che non funziona e come ti aspetti che funzioni? Da quello che ho letto penso tu quale sia la semantica dell'importazione come quella di Java o forse quella di C. Ultima cosa: puoi rendere sicuro un modulo "star-import" aggiungendo una __all__variabile che contiene un elenco dei nomi che dovrebbero essere esportati quando importati da star. edit: Ok, leggendo la risposta di BrenBarn ho capito cosa intendevi.
Bakuriu

Risposte:


69

Sembra che tu abbia frainteso il modo in cui importcerca i moduli. Quando si utilizza un'istruzione import, viene sempre eseguita la ricerca nel percorso effettivo del modulo (e / o sys.modules); non fa uso di oggetti modulo nello spazio dei nomi locale che esistono a causa di precedenti importazioni. Quando lo fai:

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

La seconda riga cerca un pacchetto chiamato package.subpackagee importa moduleda quel pacchetto. Questa riga non ha effetto sulla terza riga. La terza riga cerca solo un modulo chiamato modulee non ne trova uno. Non "riutilizza" l'oggetto chiamato moduleche hai ottenuto dalla riga sopra.

In altre parole from someModule import ...non significa "dal modulo chiamato someModule che ho importato in precedenza ..." significa "dal modulo chiamato someModule che trovi su sys.path ...". Non c'è modo di costruire "incrementalmente" il percorso di un modulo importando i pacchetti che portano ad esso. Devi sempre fare riferimento all'intero nome del modulo durante l'importazione.

Non è chiaro cosa stai cercando di ottenere. Se vuoi importare solo il particolare attributo1 dell'oggetto, fallo from package.subpackage.module import attribute1e basta. Non devi mai preoccuparti del tempo package.subpackage.moduledopo aver importato il nome che desideri da esso.

Se non vuoi avere accesso al modulo per accedere ad altri nomi più tardi, allora si può fare from package.subpackage import modulee, come avete visto si può poi fare module.attribute1e così via, per quanto ti piace.

Se vuoi entrambi --- cioè, se vuoi attribute1direttamente accessibile e vuoi moduleaccessibile, fai entrambe le cose sopra:

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

Se non ti piace digitare package.subpackagenemmeno due volte, puoi semplicemente creare manualmente un riferimento locale ad attributo1:

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

I tuoi commenti vanno nella stessa direzione di quelli di Ignacio Vazquez-Abrams (ho commentato il suo messaggio). La tua aggiunta alla fine, sull'utilizzo module.attribute1è qualcosa a cui ho pensato, ma ho pensato che ci sarebbe stato un modo per evitare la necessità di un prefisso ovunque. Quindi devo usare un prefisso ogni dove, o creare un alias locale, ripetendo il nome. Non lo stile che mi aspettavo, ma se non c'è modo (dopotutto, sono abituato ad Ada, che richiede qualcosa di simile con le sue dichiarazioni di ridenominazione).
Hibou57

@ Hibou57: Non mi è ancora chiaro cosa stai cercando di ottenere nella tua "Versione 2". Cosa vuoi fare che non sia possibile? Non vuoi mai ridigitare nessuna parte del nome del pacchetto / modulo / attributo, ma importare comunque sia il modulo che il suo attributo?
BrenBarn

Volevo avere un riferimento a un pacchetto locale, proprio come puoi avere un riferimento a un oggetto locale. Sembra che ci siano finalmente riferimenti ai moduli locali, ma non è possibile importarli da questi. È un mix di locale e globale con un gusto divertente (alcune cose possono essere locali, altre devono essere globali, non mi piace, ma sto bene fintanto che ora capisco meglio come funziona). Grazie per il tuo messaggio, comunque.
Hibou57

1
Non sono sicuro che tu capisca ancora come funziona. O che hai fatto nel 2012 in ogni caso.
Hejazzman

1
Ogni volta che torno a Python dopo un licenziamento di 6 mesi, finisco qui. Se solo potessi votare a favore ogni volta che visito questa pagina! Realizzerò un poster gigantesco con questa frase: "Non c'è modo di costruire" in modo incrementale "il percorso di un modulo importando i pacchetti che portano ad esso."
PatrickT

10

Il motivo # 2 fallisce è perché sys.modules['module']non esiste (la routine di importazione ha il suo ambito e non può vedere il modulenome locale) e non c'è alcun modulemodulo o pacchetto su disco. Tieni presente che puoi separare più nomi importati con virgole.

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

Anche:

from package.subpackage import module
print module.attribute1

Il tuo riferimento a sys.modules['name']cui non sapevo fino ad ora, mi ha fatto pensare che fosse quello che avevo paura (e BrenBarn conferma): non c'è niente come veri sotto-pacchetti in Python. sys.modules, come suggerisce il nome, è globale e se tutti i riferimenti ai moduli si basano su questo, allora non c'è niente come un riferimento locale a un modulo (potrebbe essere disponibile con Python 3.x?).
Hibou57

Il tuo uso di "riferimento" è ambiguo; il primo importin # 2 genera un riferimento locale a cui è package.subpackage.moduleassociato module.
Ignacio Vazquez-Abrams

Sì, ma quello è un "modulo" da cui non posso importare ;-)
Hibou57

0

Se tutto ciò che stai cercando di fare è ottenere attributo1 nel tuo spazio dei nomi globale, la versione 3 sembra a posto. Perché è un prefisso eccessivo?

Nella versione 2, invece di

from module import attribute1

tu puoi fare

attribute1 = module.attribute1

attribute1 = module.attribute1sta solo ripetendo il nome senza alcun valore aggiunto. So che funziona, ma non mi piace questo stile (il che non significa che non mi piaccia la tua risposta).
Hibou57

2
Immagino che, come tutte le persone che commentano qui, non capisco cosa vuoi fare. In tutti gli esempi che fornisci, sembra che tu voglia finire con un simbolo da un sottopacchetto nel tuo spazio dei nomi. Il tuo esempio non funzionante (esempio 2) vuole farlo importando un sottomodulo da un pacchetto e quindi importando un simbolo da quel sottomodulo. Non so perché vuoi farlo in due passaggi invece di uno. Forse spiega di più quale sarebbe la tua soluzione ideale e perché.
Thomas Vander Stichele
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.