Cosa succederà se due moduli si importano l'un l'altro?
Per generalizzare il problema, che dire delle importazioni cicliche in Python?
Cosa succederà se due moduli si importano l'un l'altro?
Per generalizzare il problema, che dire delle importazioni cicliche in Python?
Risposte:
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.
Se lo fai import foo
dentro bar
e import bar
dentro 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 abc
e 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.
from foo import *
e from bar import *
funziona anche bene.
from x import y
, eppure ottiene ancora l'errore di importazione circolare
import
viene eseguita l' istruzione. Quindi non si guasterà ma potresti non ottenere tutte le variabili che ti aspetti.
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 bar
non sono state ancora definite ...
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 b
nuovo, perché esiste già nel modulo dict.
Se si tenta di accedere b.x
da a
durante 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.x
si accede, la linea x = 3
non è stata ancora eseguita, cosa che succederà solo dopo b out
.
__name__
invece di 'a'
. All'inizio, ero totalmente confuso sul perché un file sarebbe stato eseguito due volte.
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 ImportError
errori, a causa del modo in cui import()
valuta le dichiarazioni di livello superiore dell'intero file quando viene chiamato.
Questi ImportErrors
possono quasi sempre essere evitati se desideri positivamente le tue importazioni in cima :
Considera questa importazione circolare:
# profiles/serializers.py
from images.serializers import SimplifiedImageSerializer
class SimplifiedProfileSerializer(serializers.Serializer):
name = serializers.CharField()
class ProfileSerializer(SimplifiedProfileSerializer):
recent_images = SimplifiedImageSerializer(many=True)
# 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:00
ecco 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 SimplifiedImageSerializer
e se ImportError
viene generato, poiché è già stato importato, lo estrarrà dalla importcache.
PS: Devi leggere l'intero post con la voce di David Beazley.
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
import bar
in foo.py
fino alla fine
bar
ed foo
entrambi devono usare gX
, la soluzione "più pulita" è quella di inserire gX
un altro modulo, avere entrambi foo
e bar
importare quel modulo. (più pulito nel senso che non ci sono dipendenze semantiche nascoste.)
bar
non riesco nemmeno a trovare gX
nel foo. l'importazione circolare va bene da sola, ma è solo che gX
non è definita quando viene importata.
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:
import b
. quindi visiterà il modulo bimport a
. quindi visiterà il modulo aimport b
ma 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"
."This is from module b"
"This is from module a"
e il programma sarà terminato.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!
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_result
metodo 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 staticmethod
decoratore 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.
Le importazioni circolari possono essere fonte di confusione perché l'importazione fa due cose:
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
Ho risolto il problema nel modo seguente e funziona bene senza errori. Considera due file a.py
e b.py
.
Ho aggiunto questo a.py
e ha funzionato.
if __name__ == "__main__":
main ()
import b
y = 2
def main():
print ("a out")
print (b.x)
if __name__ == "__main__":
main ()
import a
print ("b out")
x = 3 + a.y
L'output che ottengo è
>>> b out
>>> a out
>>> 5
Ok, penso di avere una soluzione piuttosto interessante. Diciamo che hai file a
e file b
. Si dispone di un def
o un class
in un file b
che si desidera utilizzare nel modulo a
, ma è necessario qualcosa di diverso, sia una def
, class
o una variabile da file a
di 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 a
che è necessaria nel file b
, ma prima di chiamare la funzione o la classe dal file b
che ti serve per il file a
, dì import b
Quindi, ed ecco la parte chiave , in tutte le definizioni o classi nel file b
che richiedono il file def
o class
daa
(chiamiamolo CLASS
), dicifrom a import CLASS
Questo funziona perché puoi importare file b
senza che Python esegua nessuna delle istruzioni di importazione nel file b
, e quindi eludi qualsiasi importazione circolare.
Per esempio:
class A(object):
def __init__(self, name):
self.name = name
CLASS = A("me")
import b
go = B(6)
go.dostuff
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 CLASS
non 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 a
ovunque in b.py.