Importazioni circolari (o cicliche) in Python


352

Cosa succederà se due moduli si importano l'un l'altro?

Per generalizzare il problema, che dire delle importazioni cicliche in Python?



1
anche solo come riferimento, sembra che le importazioni circolari siano consentite su Python 3.5 (e probabilmente oltre) ma non 3.4 (e probabilmente sotto).
Charlie Parker,

4
Sto usando Python 3.7.2 e sto ancora riscontrando un errore di runtime a causa delle dipendenze circolari.
Richard Whitehead,

Risposte:


281

Lo scorso anno si è svolto un ottimo dibattito su comp.lang.python . Risponde alla tua domanda piuttosto accuratamente.

Le importazioni sono davvero molto semplici. Ricorda solo quanto segue:

'import' e 'from xxx import yyy' sono istruzioni eseguibili. Eseguono quando il programma in esecuzione raggiunge quella linea.

Se un modulo non è in sys.modules, un'importazione crea la nuova voce del modulo in sys.modules e quindi esegue il codice nel modulo. Non restituisce il controllo al modulo chiamante fino al completamento dell'esecuzione.

Se un modulo esiste in sys.modules, un'importazione restituisce semplicemente quel modulo indipendentemente dal fatto che abbia completato l'esecuzione. Questo è il motivo per cui le importazioni cicliche possono restituire moduli che sembrano essere parzialmente vuoti.

Infine, lo script in esecuzione viene eseguito in un modulo chiamato __main__, importando lo script con il proprio nome creerà un nuovo modulo non correlato a __main__.

Prendi tutto insieme e non dovresti avere sorprese durante l'importazione di moduli.


13
@meawoppl Potresti espandere questo commento, per favore? Come sono cambiati in modo specifico?
Dan Schien,

3
A partire da ora, l'unico riferimento alle importazioni circolari in python3 "Novità?" le pagine sono nella 3.5 . Dice "Sono ora supportate le importazioni circolari che coinvolgono le importazioni relative". @meawoppl hai trovato qualcos'altro che non è elencato in queste pagine?
Zezollo,

4
Sono def. non supportato in 3.0-3.4. O almeno la semantica del successo è diversa. Ecco una sinossi che ho scoperto che non menzioniamo le 3.5 modifiche. gist.github.com/datagrok/40bf84d5870c41a77dc6
meawoppl

Per favore, puoi espandere questo "Infine, lo script in esecuzione viene eseguito in un modulo chiamato main , importando lo script con il proprio nome creerà un nuovo modulo non correlato a main .". Quindi supponiamo che il file sia a.py e quando viene eseguito come punto di ingresso principale, ora è il principale se ha un codice come da un'importazione una variabile. Quindi lo stesso file 'a.py' verrà caricato nella tabella dei moduli sys? Quindi significa che se ha detto print statement, verrà eseguito due volte? Una volta per il file principale e di nuovo quando si incontra l'importazione?
variabile

Questa risposta ha 10 anni e vorrei un aggiornamento modernizzato per garantire che rimanga corretto in varie versioni di Python, 2.xo 3.x
Fallenreaper,

296

Se lo fai import foodentro bare import bardentro foo, funzionerà bene. Quando tutto funzionerà effettivamente, entrambi i moduli saranno completamente caricati e avranno riferimenti reciproci.

Il problema è quando invece lo fai from foo import abce from bar import xyz. Perché ora ogni modulo richiede che l'altro modulo sia già stato importato (in modo che esista il nome che stiamo importando) prima di poter essere importato.


27
Sembra che from foo import *e from bar import *funziona anche bene.
Akavall,

1
Controlla la modifica al post sopra usando a.py/b.py. Non usa from x import y, eppure ottiene ancora l'errore di importazione circolare
Greg Ennis,

2
Questo non è del tutto vero. Proprio come import * da, se si tenta di accedere a un elemento nell'importazione circolare, al livello superiore, quindi prima che lo script completi la sua esecuzione, si avrà lo stesso problema. Ad esempio, se si sta impostando un pacchetto globale in un pacchetto da un altro ed entrambi si includono. Lo stavo facendo per creare una fabbrica sciatta per un oggetto nella classe base in cui quell'oggetto potesse essere una delle numerose sottoclassi e il codice usando non aveva bisogno di essere consapevole di ciò che stava effettivamente creando.
AaronM,

3
@Akavall Non proprio. Ciò importerà solo i nomi disponibili quando importviene eseguita l' istruzione. Quindi non si guasterà ma potresti non ottenere tutte le variabili che ti aspetti.
Augurar

3
Nota, se lo fai from foo import *e from bar import *, tutto ciò che viene eseguito in fooè nella fase di inizializzazione di bar, e le funzioni effettive in barnon sono state ancora definite ...
Martian2049

100

Le importazioni cicliche terminano, ma è necessario fare attenzione a non utilizzare i moduli importati ciclicamente durante l'inizializzazione del modulo.

Considera i seguenti file:

a.py:

print "a in"
import sys
print "b imported: %s" % ("b" in sys.modules, )
import b
print "a out"

b.py:

print "b in"
import a
print "b out"
x = 3

Se esegui a.py, otterrai quanto segue:

$ python a.py
a in
b imported: False
b in
a in
b imported: True
a out
b out
a out

Alla seconda importazione di b.py (nella seconda a in), l'interprete Python non importa di bnuovo, perché esiste già nel modulo dict.

Se si tenta di accedere b.xda adurante l'inizializzazione del modulo, si otterrà una AttributeError.

Aggiungi la seguente riga a a.py:

print b.x

Quindi, l'output è:

$ python a.py
a in                    
b imported: False
b in
a in
b imported: True
a out
Traceback (most recent call last):
  File "a.py", line 4, in <module>
    import b
  File "/home/shlomme/tmp/x/b.py", line 2, in <module>
    import a
 File "/home/shlomme/tmp/x/a.py", line 7, in <module>
    print b.x
AttributeError: 'module' object has no attribute 'x'

Questo perché i moduli vengono eseguiti all'importazione e al momento b.xsi accede, la linea x = 3non è stata ancora eseguita, cosa che succederà solo dopo b out.


14
questo spiega notevolmente il problema, ma per quanto riguarda la soluzione? come potremmo importare e stampare correttamente x? l'altra soluzione sopra non ha funzionato per me
mehmet

Penso che questa risposta trarrebbe un grande beneficio se tu usassi __name__invece di 'a'. All'inizio, ero totalmente confuso sul perché un file sarebbe stato eseguito due volte.
Bergi,

30

Come altre risposte descrivono questo modello è accettabile in Python:

def dostuff(self):
     from foo import bar
     ...

Ciò eviterà l'esecuzione dell'istruzione import quando il file viene importato da altri moduli. Solo se esiste una dipendenza circolare logica, ciò fallirà.

La maggior parte delle importazioni circolari non sono in realtà importazioni circolari logiche, ma piuttosto generano ImportErrorerrori, a causa del modo in cui import()valuta le dichiarazioni di livello superiore dell'intero file quando viene chiamato.

Questi ImportErrorspossono quasi sempre essere evitati se desideri positivamente le tue importazioni in cima :

Considera questa importazione circolare:

App A

# profiles/serializers.py

from images.serializers import SimplifiedImageSerializer

class SimplifiedProfileSerializer(serializers.Serializer):
    name = serializers.CharField()

class ProfileSerializer(SimplifiedProfileSerializer):
    recent_images = SimplifiedImageSerializer(many=True)

App B

# images/serializers.py

from profiles.serializers import SimplifiedProfileSerializer

class SimplifiedImageSerializer(serializers.Serializer):
    title = serializers.CharField()

class ImageSerializer(SimplifiedImageSerializer):
    profile = SimplifiedProfileSerializer()

Da David Beazley, eccellenti moduli e pacchetti: Live and Let Die! - PyCon 2015 , 1:54:00ecco un modo per gestire le importazioni circolari in Python:

try:
    from images.serializers import SimplifiedImageSerializer
except ImportError:
    import sys
    SimplifiedImageSerializer = sys.modules[__package__ + '.SimplifiedImageSerializer']

Questo tenta di importare SimplifiedImageSerializere se ImportErrorviene generato, poiché è già stato importato, lo estrarrà dalla importcache.

PS: Devi leggere l'intero post con la voce di David Beazley.


9
ImportError non viene generato se il modulo è già stato importato. I moduli possono essere importati tutte le volte che vuoi, ad esempio "import a; import a;" va bene
Yuras,

9

Ho un esempio qui che mi ha colpito!

foo.py

import bar

class gX(object):
    g = 10

bar.py

from foo import gX

o = gX()

main.py

import foo
import bar

print "all done"

Alla riga di comando: $ python main.py

Traceback (most recent call last):
  File "m.py", line 1, in <module>
    import foo
  File "/home/xolve/foo.py", line 1, in <module>
    import bar
  File "/home/xolve/bar.py", line 1, in <module>
    from foo import gX
ImportError: cannot import name gX

2
Come hai risolto questo problema? Sto cercando di capire l'importazione circolare per risolvere un mio problema molto simile a quello che stai facendo ...
c089

12
Errm ... Penso di aver risolto il mio problema con questo hack incredibilmente brutto. {{{se non 'foo.bar' in sys.modules: da foo import bar else: bar = sys.modules ['foo.bar']}}} Personalmente, penso che le importazioni circolari siano un ENORME segnale di avvertimento su un codice errato design ...
c089

5
@ C089, o si può solo andare import barin foo.pyfino alla fine
warvariuc

5
Se bared fooentrambi devono usare gX, la soluzione "più pulita" è quella di inserire gXun altro modulo, avere entrambi fooe barimportare quel modulo. (più pulito nel senso che non ci sono dipendenze semantiche nascoste.)
Tim Wilder,

2
Tim ha un buon punto. Fondamentalmente è perché barnon riesco nemmeno a trovare gXnel foo. l'importazione circolare va bene da sola, ma è solo che gXnon è definita quando viene importata.
Martian2049

9

Modulo a.py:

import b
print("This is from module a")

Modulo b.py

import a
print("This is from module b")

L'esecuzione di "Modulo a" produrrà:

>>> 
'This is from module a'
'This is from module b'
'This is from module a'
>>> 

Emetteva queste 3 linee mentre doveva produrre infinito a causa dell'importazione circolare. Cosa succede riga per riga mentre si esegue "Modulo a" è elencato qui:

  1. La prima riga è import b. quindi visiterà il modulo b
  2. La prima riga del modulo b è import a. quindi visiterà il modulo a
  3. La prima riga nel modulo a è import bma nota che questa riga non verrà più eseguita di nuovo , poiché ogni file in Python esegue una riga di importazione solo per una volta, non importa dove o quando viene eseguita. quindi passerà alla riga successiva e stampa "This is from module a".
  4. Dopo aver terminato di visitare l'intero modulo a dal modulo b, siamo ancora al modulo b. così verrà stampata la riga successiva"This is from module b"
  5. Le linee del modulo b sono eseguite completamente. quindi torneremo al modulo a dove abbiamo iniziato il modulo b.
  6. la riga di importazione b è già stata eseguita e non verrà più eseguita. la riga successiva verrà stampata "This is from module a"e il programma sarà terminato.

4

Sono completamente d'accordo con la risposta di Pythoneer qui. Ma mi sono imbattuto in un codice che era difettoso con le importazioni circolari e ha causato problemi durante il tentativo di aggiungere test unitari. Quindi, per correggerlo rapidamente senza cambiare tutto ciò che è possibile risolvere il problema eseguendo un'importazione dinamica.

# Hack to import something without circular import issue
def load_module(name):
    """Load module using imp.find_module"""
    names = name.split(".")
    path = None
    for name in names:
        f, path, info = imp.find_module(name, path)
        path = [path]
    return imp.load_module(name, f, path[0], info)
constants = load_module("app.constants")

Ancora una volta, questa non è una correzione permanente, ma può aiutare qualcuno che vuole correggere un errore di importazione senza modificare troppo il codice.

Saluti!


3

Ci sono molte grandi risposte qui. Mentre di solito ci sono soluzioni rapide al problema, alcune delle quali sembrano più pitoniche di altre, se hai il lusso di fare del refactoring, un altro approccio è quello di analizzare l'organizzazione del tuo codice e provare a rimuovere la dipendenza circolare. Ad esempio, potresti scoprire di avere:

File a.py

from b import B

class A:
    @staticmethod
    def save_result(result):
        print('save the result')

    @staticmethod
    def do_something_a_ish(param):
        A.save_result(A.use_param_like_a_would(param))

    @staticmethod
    def do_something_related_to_b(param):
        B.do_something_b_ish(param)

File b.py

from a import A

class B:
    @staticmethod
    def do_something_b_ish(param):
        A.save_result(B.use_param_like_b_would(param))

In questo caso, basta spostare un metodo statico in un file separato, ad esempio c.py:

File c.py

def save_result(result):
    print('save the result')

consentirà di rimuovere il save_resultmetodo da A e quindi di rimuovere l'importazione di A da a in b:

File refactored a.py

from b import B
from c import save_result

class A:
    @staticmethod
    def do_something_a_ish(param):
        A.save_result(A.use_param_like_a_would(param))

    @staticmethod
    def do_something_related_to_b(param):
        B.do_something_b_ish(param)

File refactored b.py

from c import save_result

class B:
    @staticmethod
    def do_something_b_ish(param):
        save_result(B.use_param_like_b_would(param))

In sintesi, se si dispone di uno strumento (ad esempio pylint o PyCharm) che riporta i metodi che possono essere statici, basta lanciare un staticmethoddecoratore su di essi potrebbe non essere il modo migliore per mettere a tacere l'avvertimento. Anche se il metodo sembra correlato alla classe, potrebbe essere meglio separarlo, soprattutto se si dispone di diversi moduli strettamente correlati che potrebbero richiedere la stessa funzionalità e si intende esercitare i principi DRY.


2

Le importazioni circolari possono essere fonte di confusione perché l'importazione fa due cose:

  1. esegue il codice del modulo importato
  2. aggiunge il modulo importato alla tabella dei simboli globali del modulo di importazione

Il primo viene eseguito una sola volta, mentre il secondo in ogni istruzione di importazione. L'importazione circolare crea situazioni quando il modulo di importazione utilizza quello importato con codice parzialmente eseguito. Di conseguenza non vedrà gli oggetti creati dopo l'istruzione import. Di seguito è riportato un esempio di codice.

Le importazioni circolari non sono il male estremo da evitare a tutti i costi. In alcuni framework come Flask sono abbastanza naturali e modificare il codice per eliminarli non migliora il codice.

main.py

print 'import b'
import b
print 'a in globals() {}'.format('a' in globals())
print 'import a'
import a
print 'a in globals() {}'.format('a' in globals())
if __name__ == '__main__':
    print 'imports done'
    print 'b has y {}, a is b.a {}'.format(hasattr(b, 'y'), a is b.a)

b.by

print "b in, __name__ = {}".format(__name__)
x = 3
print 'b imports a'
import a
y = 5
print "b out"

a.py

print 'a in, __name__ = {}'.format(__name__)
print 'a imports b'
import b
print 'b has x {}'.format(hasattr(b, 'x'))
print 'b has y {}'.format(hasattr(b, 'y'))
print "a out"

python main.py output con commenti

import b
b in, __name__ = b    # b code execution started
b imports a
a in, __name__ = a    # a code execution started
a imports b           # b code execution is already in progress
b has x True
b has y False         # b defines y after a import,
a out
b out
a in globals() False  # import only adds a to main global symbol table 
import a
a in globals() True
imports done
b has y True, a is b.a True # all b objects are available

1

Ho risolto il problema nel modo seguente e funziona bene senza errori. Considera due file a.pye b.py.

Ho aggiunto questo a.pye ha funzionato.

if __name__ == "__main__":
        main ()

a.py:

import b
y = 2
def main():
    print ("a out")
    print (b.x)

if __name__ == "__main__":
    main ()

b.py:

import a
print ("b out")
x = 3 + a.y

L'output che ottengo è

>>> b out 
>>> a out 
>>> 5

0

Ok, penso di avere una soluzione piuttosto interessante. Diciamo che hai file ae file b. Si dispone di un defo un classin un file bche si desidera utilizzare nel modulo a, ma è necessario qualcosa di diverso, sia una def, classo una variabile da file adi cui avete bisogno nella vostra definizione o classe nel file di b. Quello che puoi fare è, in fondo al file a, dopo aver chiamato la funzione o la classe nel file ache è necessaria nel file b, ma prima di chiamare la funzione o la classe dal file bche ti serve per il file a, dì import b Quindi, ed ecco la parte chiave , in tutte le definizioni o classi nel file bche richiedono il file defo classdaa(chiamiamolo CLASS), dicifrom a import CLASS

Questo funziona perché puoi importare file bsenza che Python esegua nessuna delle istruzioni di importazione nel file b, e quindi eludi qualsiasi importazione circolare.

Per esempio:

File a:

class A(object):

     def __init__(self, name):

         self.name = name

CLASS = A("me")

import b

go = B(6)

go.dostuff

File b:

class B(object):

     def __init__(self, number):

         self.number = number

     def dostuff(self):

         from a import CLASS

         print "Hello " + CLASS.name + ", " + str(number) + " is an interesting number."

Ecco.


from a import CLASSnon salta in realtà l'esecuzione di tutto il codice in a.py. Questo è ciò che accade realmente: (1) Tutto il codice in a.py viene eseguito come un modulo speciale "__main__". (2) A import b, viene eseguito il codice di livello superiore in b.py (che definisce la classe B) e quindi il controllo ritorna su "__main__". (3) "__main__" alla fine passa il controllo a go.dostuff(). (4) quando arriva dostuff () import a, esegue nuovamente tutto il codice in a.py , questa volta come il modulo "a"; quindi importa l'oggetto CLASS dal nuovo modulo "a". Quindi, in realtà, funzionerebbe altrettanto bene se lo utilizzassi import aovunque in b.py.
Matthias Fripp,
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.