Importazione circolare di Python?


98

Quindi ricevo questo errore

Traceback (most recent call last):
  File "/Users/alex/dev/runswift/utils/sim2014/simulator.py", line 3, in <module>
    from world import World
  File "/Users/alex/dev/runswift/utils/sim2014/world.py", line 2, in <module>
    from entities.field import Field
  File "/Users/alex/dev/runswift/utils/sim2014/entities/field.py", line 2, in <module>
    from entities.goal import Goal
  File "/Users/alex/dev/runswift/utils/sim2014/entities/goal.py", line 2, in <module>
    from entities.post import Post
  File "/Users/alex/dev/runswift/utils/sim2014/entities/post.py", line 4, in <module>
    from physics import PostBody
  File "/Users/alex/dev/runswift/utils/sim2014/physics.py", line 21, in <module>
    from entities.post import Post
ImportError: cannot import name Post

e puoi vedere che uso la stessa dichiarazione di importazione più in alto e funziona? C'è qualche regola non scritta sull'importazione circolare? Come si usa la stessa classe più in basso nello stack di chiamate?

Risposte:


161

Penso che la risposta di jpmc26, sebbene non sia affatto sbagliata , si riduce troppo pesantemente sulle importazioni circolari. Possono funzionare benissimo, se impostati correttamente.

Il modo più semplice per farlo è usare la import my_modulesintassi, piuttosto che from my_module import some_object. Il primo funzionerà quasi sempre, anche se my_moduleincluso ci importa indietro. Quest'ultimo funziona solo se my_objectè già definito in my_module, cosa che in un'importazione circolare potrebbe non essere il caso.

Per essere specifico per il tuo caso: prova a cambiare entities/post.pyda fare import physicse poi fai riferimento a physics.PostBodypiuttosto che PostBodydirettamente. Allo stesso modo, cambia physics.pyper fare import entities.poste poi usa entities.post.Postpiuttosto che solo Post.


5
Questa risposta è compatibile con le importazioni relative?
Joe

17
Perché succede questo?
Juan Pablo Santos

4
È sbagliato dire che la non fromsintassi funzionerà sempre. Se ho class A(object): pass; class C(b.B): passnel modulo a e class B(a.A): passnel modulo b, l'importazione circolare è ancora un problema e questo non funzionerà.
CrazyCasta

1
Hai ragione, qualsiasi dipendenza circolare nel codice di primo livello dei moduli (come le classi di base delle dichiarazioni di classe nel tuo esempio) sarà un problema. Questo è il tipo di situazione in cui la risposta di jpmc che dovresti rifattorizzare l'organizzazione del modulo è probabilmente corretta al 100%. O sposta la classe Bnel modulo ao sposta la classe Cnel modulo in bmodo da poter interrompere il ciclo. Vale anche la pena notare che anche se solo una direzione del cerchio ha il codice di primo livello coinvolto (ad esempio se la classe Cnon esiste), potresti ricevere un errore, a seconda di quale modulo è stato importato per primo da un altro codice.
Blckknght

2
@TylerCrompton: Non sono sicuro di cosa intendi per "l'importazione del modulo deve essere assoluta". Le importazioni relative circolari possono funzionare, purché si importino moduli, non il loro contenuto (ad esempio from . import sibling_module, non from .sibling_module import SomeClass). C'è un po 'più di sottigliezza quando il __init__.pyfile di un pacchetto è coinvolto nell'importazione circolare, ma il problema è raro e probabilmente un bug importnell'implementazione. Vedere il bug di Python 23447 , per il quale ho inviato una patch (che purtroppo sta languendo).
Blckknght

51

Quando si importa un modulo (o un membro di esso) per la prima volta, il codice all'interno del modulo viene eseguito in sequenza come qualsiasi altro codice; ad esempio, non viene trattato in modo diverso rispetto al corpo di una funzione. Una importè solo un comando come qualsiasi altro (assegnazione, una chiamata di funzione, def, class). Supponendo che le tue importazioni avvengano all'inizio dello script, ecco cosa sta succedendo:

  • Quando si tenta di importare Worldda world, lo worldscript viene eseguito.
  • Lo worldscript importa Field, il che fa sì che lo entities.fieldscript venga eseguito.
  • Questo processo continua finché non raggiungi lo entities.postscript perché hai tentato di importarePost
  • Lo entities.postscript causa l' physicsesecuzione del modulo perché tenta di importarePostBody
  • Infine, physicsprova a importare Postdaentities.post
  • Non sono sicuro che il entities.postmodulo esista ancora in memoria, ma non importa. O il modulo non è in memoria o il modulo non ha ancora un Postmembro perché non ha terminato l'esecuzione per definirePost
  • In ogni caso, si verifica un errore perché Postnon deve essere importato

Quindi no, non sta "lavorando più in alto nello stack di chiamate". Questa è una traccia dello stack del punto in cui si è verificato l'errore, il che significa che si è verificato un errore durante il tentativo di importazione Postin quella classe. Non dovresti usare importazioni circolari. Nella migliore delle ipotesi, ha un vantaggio trascurabile (in genere, nessun vantaggio) e causa problemi come questo. Obbliga qualsiasi sviluppatore a mantenerlo, costringendolo a camminare sui gusci d'uovo per evitare di romperlo. Rifattorizza la tua organizzazione del modulo.


1
Dovrebbe essere isinstance(userData, Post). Indipendentemente da ciò, non hai scelta. L'importazione circolare non funzionerà. Il fatto che tu abbia importazioni circolari per me è un odore di codice. Suggerisce di avere alcune funzionalità che dovrebbero essere spostate in un terzo modulo. Non potrei dire cosa senza guardare entrambe le classi intere.
jpmc26

3
@CpILL Dopo un po ', mi è venuta in mente un'opzione molto hacker. Se non è possibile andare in giro a fare questo per ora (a causa di vincoli di tempo o quello che hai), allora si potrebbe fare il vostro importazione a livello locale all'interno del metodo in cui lo si usa. Il corpo di una funzione all'interno defnon viene eseguito fino a quando la funzione non viene chiamata, quindi l'importazione non verrà eseguita fino a quando non si chiama effettivamente la funzione. A quel punto, i messaggi di posta importelettronica dovrebbero funzionare poiché uno dei moduli sarebbe stato completamente importato prima della chiamata. Questo è un trucco assolutamente disgustoso e non dovrebbe rimanere nella tua base di codice per un periodo di tempo significativo.
jpmc26

15
Penso che la tua risposta sia troppo dura sulle importazioni circolari. Le importazioni circolari di solito funzionano se fai solo import foopiuttosto che from foo import Bar. Questo perché la maggior parte dei moduli definisce solo cose (come funzioni e classi) che vengono eseguite in seguito. I moduli che fanno cose significative quando vengono importati (come uno script non protetto da if __name__ == "__main__") potrebbero ancora essere problemi, ma non è troppo comune.
Blckknght

6
@Blckknght Penso che ti stia preparando per passare del tempo su strani problemi che altre persone dovranno indagare e da cui saranno confusi se utilizzi le importazioni circolari. Ti costringono a passare il tempo facendo attenzione a non inciampare su di loro, e per di più c'è un odore di codice che il tuo progetto ha bisogno di refactoring. Potrei essermi sbagliato sul fatto che siano tecnicamente fattibili, ma sono una pessima scelta di design destinata a causare problemi prima o poi. La chiarezza e la semplicità sono il Sacro Graal nella programmazione e le importazioni circolari violano entrambe nel mio libro.
jpmc26

6
In alternativa; hai diviso troppo le tue funzionalità e questa è la causa delle importazioni circolari. Se hai due cose che dipendono l'una dall'altra tutto il tempo ; potrebbe essere meglio metterli in un unico file. Python non è Java; nessun motivo per non raggruppare funzionalità / classi in un unico file per evitare strane logiche di importazione. :-)
Mark Ribau

40

Per comprendere le dipendenze circolari, è necessario ricordare che Python è essenzialmente un linguaggio di scripting. L'esecuzione di istruzioni al di fuori dei metodi avviene in fase di compilazione. Le istruzioni di importazione vengono eseguite proprio come le chiamate di metodo e per comprenderle dovresti pensarle come chiamate di metodo.

Quando si esegue un'importazione, ciò che accade dipende dal fatto che il file che si sta importando sia già presente nella tabella del modulo. Se lo fa, Python usa tutto ciò che è attualmente nella tabella dei simboli. In caso contrario, Python inizia a leggere il file del modulo, compilando / eseguendo / importando tutto ciò che trova lì. I simboli a cui si fa riferimento in fase di compilazione vengono trovati o meno, a seconda che siano stati visti o debbano ancora essere visti dal compilatore.

Immagina di avere due file sorgente:

File X.py

def X1:
    return "x1"

from Y import Y2

def X2:
    return "x2"

File Y.py

def Y1:
    return "y1"

from X import X1

def Y2:
    return "y2"

Supponiamo ora di compilare il file X.py. Il compilatore inizia definendo il metodo X1, quindi preme l'istruzione import in X.py. Ciò fa sì che il compilatore sospenda la compilazione di X.py e inizi a compilare Y.py. Poco dopo il compilatore preme l'istruzione import in Y.py. Poiché X.py è già nella tabella del modulo, Python utilizza la tabella dei simboli X.py incompleta esistente per soddisfare qualsiasi riferimento richiesto. Tutti i simboli che compaiono prima dell'istruzione import in X.py sono ora nella tabella dei simboli, ma i simboli dopo non lo sono. Poiché X1 ora appare prima dell'istruzione import, viene importato correttamente. Python riprende quindi a compilare Y.py. In tal modo definisce Y2 e termina la compilazione di Y.py. Quindi riprende la compilazione di X.py e trova Y2 nella tabella dei simboli Y.py. La compilazione alla fine viene completata senza errori.

Qualcosa di molto diverso accade se provi a compilare Y.py dalla riga di comando. Durante la compilazione di Y.py, il compilatore preme l'istruzione import prima che definisca Y2. Quindi inizia a compilare X.py. Presto colpisce l'istruzione import in X.py che richiede Y2. Ma Y2 non è definito, quindi la compilazione non riesce.

Nota che se modifichi X.py per importare Y1, la compilazione avrà sempre successo, indipendentemente dal file che compili. Tuttavia, se modifichi il file Y.py per importare il simbolo X2, nessuno dei due file verrà compilato.

Ogni volta che il modulo X, o qualsiasi modulo importato da X potrebbe importare il modulo corrente, NON utilizzare:

from X import Y

Ogni volta che pensi che ci possa essere un'importazione circolare, dovresti anche evitare i riferimenti in fase di compilazione a variabili in altri moduli. Considera il codice dall'aspetto innocente:

import X
z = X.Y

Supponiamo che il modulo X importi questo modulo prima che questo modulo importi X. Supponiamo inoltre che Y sia definito in X dopo l'istruzione import. Quindi Y non verrà definito quando questo modulo viene importato e verrà visualizzato un errore di compilazione. Se questo modulo importa prima Y, puoi farla franca. Ma quando uno dei tuoi colleghi cambia innocentemente l'ordine delle definizioni in un terzo modulo, il codice si interrompe.

In alcuni casi è possibile risolvere le dipendenze circolari spostando un'istruzione di importazione in basso sotto le definizioni dei simboli richieste da altri moduli. Negli esempi precedenti, le definizioni prima dell'istruzione import non falliscono mai. Le definizioni dopo l'istruzione import a volte falliscono, a seconda dell'ordine di compilazione. Puoi persino inserire istruzioni di importazione alla fine di un file, a condizione che nessuno dei simboli importati sia necessario in fase di compilazione.

Nota che spostare le istruzioni di importazione verso il basso in un modulo oscura ciò che stai facendo. Compensa questo con un commento nella parte superiore del tuo modulo simile al seguente:

#import X   (actual import moved down to avoid circular dependency)

In generale questa è una cattiva pratica, ma a volte è difficile da evitare.


2
Non penso che ci sia tempo di compilazione o compilazione in python
pkqxdd

6
Python ha un compilatore, e viene compilato @pkqxdd, la compilazione è solo di solito nascosto da parte dell'utente. Questo potrebbe creare un po 'di confusione, ma sarebbe difficile per l'autore fornire questa descrizione mirabilmente chiara di quello che sta succedendo senza alcun riferimento al "tempo di compilazione" di Python, alquanto oscurato.
Hank


Sono andato avanti per provarlo sulla mia macchina e ho ottenuto un risultato diverso. Ho eseguito X.py ma ho ricevuto l'errore "impossibile importare il nome" Y2 "da" Y "". Ran Y.py senza problemi però. Sono su Python 3.7.5, potresti aiutarmi a spiegare qual è il problema qui?
xuefeng huang

18

Per quelli di voi che, come me, arrivano a questo numero da Django, dovrebbero sapere che i documenti forniscono una soluzione: https://docs.djangoproject.com/en/1.10/ref/models/fields/#foreignkey

"... Per fare riferimento a modelli definiti in un'altra applicazione, è possibile specificare esplicitamente un modello con l'etichetta dell'applicazione completa. Ad esempio, se il modello del produttore sopra è definito in un'altra applicazione chiamata produzione, è necessario utilizzare:

class Car(models.Model):
    manufacturer = models.ForeignKey(
        'production.Manufacturer',
        on_delete=models.CASCADE,
)

Questo tipo di riferimento può essere utile quando si risolvono le dipendenze di importazione circolare tra due applicazioni. ... "


6
So che non dovrei usare il commento per dire "grazie", ma questo mi tormenta da alcune ore. Grazie, grazie, grazie!!!
MikeyE

Sono d'accordo con @MikeyE. Ho letto diversi blog e Stackoverflow cercando di rimediare a questo problema con PonyORM. Dove altri dicono che è una cattiva pratica, o perché dovresti codificare le tue classi in modo circolare, beh, gli ORM sono esattamente dove questo accade. Poiché molti esempi mettono tutti i modelli nello stesso file e noi seguiamo quegli esempi, tranne che usiamo un modello per file, il problema non è chiaro quando Python non riesce a compilare. Tuttavia, la risposta è così semplice. Come ha sottolineato Mike, grazie mille.
trash 80

4

Sono stato in grado di importare il modulo all'interno della funzione (solo) che richiederebbe gli oggetti da questo modulo:

def my_func():
    import Foo
    foo_instance = Foo()

che eleganza di pitone
Yaro

2

Se si verifica questo problema in un'app piuttosto complessa, può essere complicato eseguire il refactoring di tutte le importazioni. PyCharm offre una correzione rapida per questo che cambierà automaticamente anche l'uso dei simboli importati.

inserisci qui la descrizione dell'immagine


0

Stavo usando il seguente:

from module import Foo

foo_instance = Foo()

ma per sbarazzarmene circular referenceho fatto quanto segue e ha funzionato:

import module.foo

foo_instance = foo.Foo()
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.