Ci sono un sacco di queste domande di importazione circolare python
sul web. Ho scelto di contribuire a questo thread perché la query ha un commento di Ray Hettinger che legittima il caso d'uso di un'importazione circolare, ma raccomanda una soluzione che ritengo non sia particolarmente buona pratica: spostare l'importazione su un metodo.
A parte l'autorità di Hettinger, tre declinazioni di responsabilità a obiezioni comuni sono necessari:
- Non ho mai programmato in Java. Non sto cercando di fare lo stile Java.
- Il refactoring non è sempre utile o efficace.Le API logiche a volte dettano una struttura che rende inevitabili i riferimenti di importazione ricorsivi. Ricorda, il codice esiste per gli utenti, non per i programmatori.
- Combinare moduli di dimensioni piuttosto elevate può causare problemi di leggibilità e manutenibilità che possono essere molto peggiori di una o due importazioni ricorsive.
Inoltre, credo che la manutenibilità e la leggibilità impone che le importazioni siano raggruppati nella parte superiore del file, si verificano solo una volta per ogni nome necessario, e che lo stile from module import name
è preferibile (tranne forse per i nomi di modulo molto brevi con molte funzioni, ad esempio gtk
), in quanto evita il disordine verbale ripetitivo e rende esplicite le dipendenze.
Con quello fuori mano, presenterò una versione semplificata del mio caso d'uso personale che mi ha portato qui e fornire la mia soluzione.
Ho due moduli, ognuno dei quali definisce molte classi. surface
definisce superfici geometriche come piani, sfere, iperboloidi, ecc. path
definisce figure geometriche planari come linee, cerchi iperbole, ecc. Logicamente, queste sono categorie distinte e il refactoring non è un'opzione dal punto di vista dei requisiti API. Tuttavia, queste due categorie sono intime.
Un'operazione utile interseca due superfici, ad esempio l'intersezione di due piani è una linea, oppure l'intersezione di un piano e una sfera è un cerchio.
Se, per esempio, in surface.py
si fa l'importazione dritto in avanti necessario per implementare il valore di ritorno per un'operazione di intersezione:
from path import Line
si ottiene:
Traceback (most recent call last):
File "surface.py", line 62, in <module>
from path import Line
File ".../path.py", line 25, in <module>
from surface import Plane
File ".../surface.py", line 62, in <module>
from path import Line
ImportError: cannot import name Line
Geometricamente, gli aerei sono usati per definire i percorsi, dopo tutto, possono essere orientati arbitrariamente in tre (o più) dimensioni. Il traceback indica sia ciò che sta accadendo che la soluzione.
è sufficiente sostituire l'istruzione import in surface.py
con:
try: from path import Line
except ImportError: pass # skip circular import second pass
La sequenza di operazioni nella traccia posteriore è ancora accadendo. È solo che la seconda volta, ignoriamo il fallimento dell'importazione. Questo non importa, dal momento che Line
non è utilizzato a livello di modulo. Pertanto, lo spazio dei nomi necessario di surface
viene caricato in path
. L'analisi dello spazio dei nomi di path
può quindi essere completata, consentendo di caricarla in surface
, completando il primo incontro con from path import Line
. Pertanto, l'analisi dello spazio dei nomi di surface
può procedere e completare, proseguendo su qualsiasi altra cosa possa essere necessaria.
È un linguaggio facile e molto chiaro. La sintassi try: ... except ...
documenta in modo chiaro e conciso il problema delle importazioni circolari, facilitando qualsiasi manutenzione futura possa essere richiesta. Usalo ogni volta che un refactator è davvero una cattiva idea.
Non è necessario rimuovere l'importazione circolare per una buona progettazione. Spostare l'importazione in una definizione di metodo è un modo ragionevole per rinviare un'importazione. –