2009-06-20 10 views
6

Sono abituato a quel Python che consente alcuni trucchi per delegare funzionalità ad altri oggetti. Un esempio è la delega per contenere oggetti.Emulazione del test di appartenenza in Python: delega __contains__ all'oggetto contenuto correttamente

Ma cuciture, che non ho la fortuna, quando ho voglia di delegare __contains __:

class A(object): 
    def __init__(self): 
     self.mydict = {} 
     self.__contains__ = self.mydict.__contains__ 

a = A() 
1 in a 

ottengo:

Traceback (most recent call last): 
File "<stdin>", line 1, in <module> 
TypeError: argument of type 'A' is not iterable 

Quello che sto facendo di sbagliato? Quando chiamo a .__ contiene __ (1), tutto va liscio. Ho anche provato a definire un metodo __iter __ in A per rendere A un aspetto più iterabile, ma non è stato d'aiuto. Cosa mi manca qui?

risposta

18

metodi speciali come __contains__ sono solo speciale quando definito sulla classe, non sulla istanza (se non in classi legacy in Python 2, che si dovrebbe non utilizzare comunque).

Quindi, fate la vostra delegazione a livello di classe:

class A(object): 
    def __init__(self): 
     self.mydict = {} 

    def __contains__(self, other): 
     return self.mydict.__contains__(other) 

avrei in realtà preferisco di precisare quest'ultimo come return other in self.mydict, ma questo è un problema di stile minore.

Edit: se e quando "totalmente dinamica reindirizzamento per-istanza di metodi speciali" (come le classi vecchio stile offerti) è indispensabile, non è difficile da attuare con classi di nuovo stile: è sufficiente ogni istanza che ha un così particolare bisogno di essere avvolto nella sua classe speciale. Per esempio:

class BlackMagic(object): 
    def __init__(self): 
     self.mydict = {} 
     self.__class__ = type(self.__class__.__name__, (self.__class__,), {}) 
     self.__class__.__contains__ = self.mydict.__contains__ 

In sostanza, dopo il po 'di magia nera riassegnazione self.__class__ a un oggetto di nuova classe (che si comporta proprio come il precedente, ma ha un dict vuota e non altri casi ad eccezione di questo self), ovunque in una classe vecchio stile assegnata a self.__magicname__, assegnare invece a self.__class__.__magicname__ (e assicurarsi che sia un built-in o staticmethod, non una normale funzione Python, a meno che, naturalmente, in alcuni casi diversi si desideri ricevere il self quando chiamato sull'istanza).

Per inciso, il in operatore un'istanza di questa classe è BlackMagicpiù veloce, come accade, che con qualsiasi delle soluzioni precedentemente proposte - o almeno così mi misura con la mia solita fidato -mtimeit (andando direttamente allo built-in method, invece di seguire normali percorsi di ricerca che coinvolgono ereditarietà e descrittori, rade un po 'del sovraccarico).

Un metaclasse per automatizzare l'idea di self.__class__ -per-istanza non sarebbe difficile da scrivere (potrebbe fare il lavoro sporco nel metodo __new__ della classe generata, e forse anche impostare tutti i nomi magici da assegnare effettivamente alla classe se assegnato nell'istanza, tramite __setattr__ o più, molte proprietà). Ma ciò sarebbe giustificato solo se la necessità di questa funzione fosse davvero diffusa (ad esempio, porting di un enorme progetto Python 1.5.2 che utilizza liberamente "metodi speciali per istanza" per Python moderno, incluso Python 3).

I raccomandare soluzioni "intelligenti" o "magia nera"?No, non lo faccio: quasi invariabilmente è meglio fare le cose in modo semplice e diretto. Ma "quasi" è una parola importante qui, ed è bello avere a portata di mano questi "ganci" avanzati per le rare, ma non inesistenti, situazioni in cui il loro uso potrebbe effettivamente essere giustificato.

+0

Ok, questa è una spiegazione per me, ma non è del tutto soddisfacente per me, dal momento che questo tipo di delega costa più tempo (cosa potrei salvare in classi di vecchio stile - perché non dovrei usarli ??). – Juergen

+6

"Le prestazioni ad ogni costo" non sono un buon obiettivo: "l'ottimizzazione prematura è la radice di ogni male nella programmazione" (Knuth, citando Hoare). Le classi in stile moderno sono funzionalmente più ricche, più generali, più regolari e più semplici (vedi la parte inferiore di p.103 di "Python in a Nutshell", che puoi leggere su Google Ricerca Libri - cerca "metodi per istanza"). In Python 3 e versioni successive, le classi precedenti sono finite per sempre - non legare il codice a loro per risparmiare nanosecondi di scarsa rilevanza. BTW, più veloce è quello di _inherit_ da dict (che ti dà anche una classe di nuovo stile). –

+0

Ciao Alex, grazie per la lezione di tecniche di programmazione, io non bisogno ;-) Siamo spiacenti, ma non siamo bible-studiosi, quindi basta citare alcuni bible-versi o "grandi maestri" non è sufficiente per una buona me. Le altre spiegazioni sono buone, specialmente quelle sulle classi precedenti. Non voglio restare con i vecchi - ma voglio ragionare! La soluzione ereditaria mi è venuta in mente nel frattempo ed è soddisfacente per il mio problema speciale. Ancora le classi vecchio stile sono un po 'più flessibili/dinamici in questo punto (l'override peristanza di metodi speciali potrebbe essere buono anche in altre situazioni!) – Juergen

Problemi correlati