2013-11-15 16 views
8

user.py:Rimuovere python import circolare

from story import Story 

class User: 
    ... 
    def get_stories(self): 
     story_ids = [select from database] 
     return [Story.get_by_id(id) for id in story_ids] 

story.py

from user import User 

class Story: 
    ... 
    def __init__(self, id, user_id, content): 
     self.id = id 
     self.user = User.get_by_id(user_id) 
     self.content = content 

come potete vedere, c'è un import circolare in questo programma, che causa un ImportError. Ho imparato che posso spostare l'istruzione import nella definizione del metodo per prevenire questo errore. Ma voglio ancora sapere, c'è un modo per rimuovere l'importazione circolare in questo caso, o, è necessario (per un buon design)?

+0

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. –

risposta

1

Un altro modo per attenuare la circolarità è modificare lo stile di importazione. Modificare from story import Story a import story, quindi fare riferimento alla classe come story.Story. Poiché si fa riferimento solo alla classe all'interno di un metodo, non sarà necessario accedere alla classe finché non viene chiamato il metodo, entro il quale l'importazione sarà completata correttamente. (Potrebbe essere necessario apportare questa modifica in uno o entrambi i moduli, a seconda di quale viene importato per primo.)

Il design sembra un po 'strano, tuttavia. Il tuo design è tale che le classi User e Story sono accoppiate molto strettamente - nessuna delle due può essere utilizzata senza l'altra. In tal caso, avrebbe più senso avere entrambi nello stesso modulo.

+0

Sì, penso che questo design sia un po 'strano anche ... dal momento che questa è una situazione comune, mi chiedo come dovrebbe essere un buon design? grazie – wong2

+1

Bene, questa __isn't__ una situazione comune :) –

+0

La modifica suggerita non risolverà il problema, ci sarà ancora un'importazione circolare. –

0

Come ha detto BrenBarn, la soluzione più ovvia è quella di mantenere User e Story nello stesso modulo, il che ha perfettamente senso se l'Utente deve sapere qualcosa su Story. Ora se tu davvero hai bisogno di averli in moduli distinti puoi anche riconoscere l'Utente in story.py per aggiungere il metodo get_stories. Si tratta di un commercio leggibilità/disaccoppiamento off ...

1

La soluzione più ovvia in questo caso è quello di rompere la dipendenza alla classe User completamente, modificando l'interfaccia in modo che il costruttore Story accetta un vero e proprio User, non un user_id. Ciò porta anche a un design più efficiente: ad esempio se un utente ha molte storie, lo stesso oggetto può essere dato a tutti quei costruttori.

Oltre a ciò, l'importazione di un intero modulo (ovvero story e user anziché i membri) dovrebbe funzionare: il modulo importato per primo verrà visualizzato vuoto al momento dell'importazione del secondo; tuttavia non importa in quanto il contenuto di questi moduli non viene utilizzato nell'ambito globale.

Questo è leggermente preferibile rispetto all'importazione all'interno di un metodo. L'importazione all'interno di un metodo ha un sovraccarico significativo rispetto a una sola ricerca globale del modulo (story.Story), perché deve essere eseguita per ogni chiamata al metodo; sembra che in un caso semplice il sovraccarico sia almeno di 30 volte.

1

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:

  1. Non ho mai programmato in Java. Non sto cercando di fare lo stile Java.
  2. 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.
  3. 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.