Emulazione del test di appartenenza in Python: delegare __contains__ ad oggetto contenuto correttamente

StackOverflow https://stackoverflow.com/questions/1022499

Domanda

Sono abituato a che Python consenta ad alcuni trucchi per delegare funzionalità ad altri oggetti. Un esempio è la delega ad oggetti contenuti.

Ma sembra che non ho fortuna quando voglio 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

Cosa sto sbagliando? Quando chiamo un .__ contiene __ (1), tutto va liscio. Ho anche provato a definire un metodo __iter __ in A per rendere A più simile a un iterabile, ma non ha aiutato. Cosa mi sto perdendo qui?

È stato utile?

Soluzione

Metodi speciali come __contains__ sono speciali solo quando definiti sulla classe, non sull'istanza (tranne nelle classi legacy in Python 2, che dovresti non usare comunque ).

Quindi, fai la tua delega a livello di classe:

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

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

Preferirei davvero scrivere quest'ultimo come restituire altri in self.mydict , ma questo è un problema di stile minore.

Modifica : se e quando " reindirizzamento per istanza totalmente dinamico di metodi speciali " (come le classi di vecchio stile offerte) è indispensabile, non è difficile implementarlo con classi di nuovo stile: hai solo bisogno di ogni istanza che ha un bisogno così particolare di essere racchiusa nella sua classe speciale. Ad 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 un po 'di magia nera che riassegna self .__ class__ a un nuovo oggetto di classe (che si comporta esattamente come il precedente ma ha un dict vuoto e nessun altro caso tranne questo self ), ovunque in una classe vecchio stile che assegneresti a self .__ magicname__ , invece di self .__ class __.__ magicname__ (e assicurati che sia un built-in o staticmethod , non una normale funzione Python, a meno che ovviamente in qualche caso diverso non si desideri che riceva il self quando viene chiamato sull'istanza).

Per inciso, l'operatore in su un'istanza di questa classe BlackMagic è più veloce , come accade, rispetto a una qualsiasi delle proposte precedentemente proposte soluzioni - o almeno così sto misurando con il mio solito fidato -mtimeit (andando direttamente al metodo incorporato , invece di seguire le normali rotte di ricerca che implicano eredità e descrittori, rade un po 'il sovraccarico).

Una metaclasse per automatizzare l'idea 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 assegnati sull'istanza, tramite __setattr__ o molte, molte proprietà). Ma ciò sarebbe giustificato solo se la necessità di questa funzione fosse davvero diffusa (ad esempio il porting di un enorme progetto Python 1.5.2 antico che usa liberamente "metodi speciali per istanza" nel moderno Python, incluso Python 3).

Devo raccomandare " intelligente " o "magia nera" Soluzioni? No, non lo so: quasi invariabilmente è meglio fare le cose in modo semplice e diretto. Ma "quasi" è una parola importante qui ed è bello avere a portata di mano "ganci" così avanzati per le rare, ma non inesistenti, situazioni in cui il loro uso può essere effettivamente giustificato.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top