2010-09-02 1 views
39

Supponiamo che io sono un namedtuple come questo:Come fornire un'inizializzazione aggiuntiva per una sottoclasse di namedtuple?

EdgeBase = namedtuple("EdgeBase", "left, right") 

Voglio realizzare un hash-funzione personalizzata per questo, quindi creo il seguente sottoclasse:

class Edge(EdgeBase): 
    def __hash__(self): 
     return hash(self.left) * hash(self.right) 

Poiché l'oggetto è immutabile, io vuole l'hash-valore da calcolare solo una volta, in modo da fare questo:

class Edge(EdgeBase): 
    def __init__(self, left, right): 
     self._hash = hash(self.left) * hash(self.right) 

    def __hash__(self): 
     return self._hash 

questo sembra funzionare, ma non sono davvero sicuro di s ubclassing e inizializzazione in Python, specialmente con le tuple. Ci sono delle insidie ​​a questa soluzione? C'è un modo consigliato come fare questo? Va bene? Grazie in anticipo.

+1

I test hanno dimostrato che non vale la pena memorizzare il valore hash, vedere [numero 9685] (https://mail.python.org/pipermail/python-bugs-list/2013-January/192363.html) –

risposta

44

modifica per il 2017:turns out namedtuple isn't a great idea. attrs è l'alternativa moderna.

class Edge(EdgeBase): 
    def __new__(cls, left, right): 
     self = super(Edge, cls).__new__(cls, left, right) 
     self._hash = hash(self.left) * hash(self.right) 
     return self 

    def __hash__(self): 
     return self._hash 

__new__ è quello che si desidera chiamare qui perché le tuple sono immutabili. Gli oggetti immutabili vengono creati in __new__ e quindi restituiti all'utente, anziché essere popolati con i dati in __init__.

cls deve essere passato due volte per la chiamata super su __new__ perché __new__ è, per storiche/motivi dispari implicitamente un staticmethod.

+3

Perché dovrei preferire questo alla mia soluzione (questa non è una domanda cinica, voglio davvero capire se ci sono differenze significative)? –

+9

La tua soluzione non usa 'super', e quindi interromperà qualsiasi tipo di situazione di ereditarietà multipla. Non importa se * tu * non usi l'MI; se qualcun altro lo fa, il loro codice si romperà orribilmente. Non è difficile evitare questo problema semplicemente usando 'super' dappertutto. – habnabit

+5

È anche utile aggiungere '__slots__ =()' alla classe. Altrimenti verrà creato '__dict__', annullando l'efficienza della memoria di' namedtuple'. – WGH

3

Il codice nella domanda potrebbe trarre vantaggio da una super chiamata nel __init__ nel caso in cui venga mai sottoclasa in una situazione di ereditarietà multipla, ma altrimenti è corretta.

class Edge(EdgeBase): 
    def __init__(self, left, right): 
     super(Edge, self).__init__(a, b) 
     self._hash = hash(self.left) * hash(self.right) 

    def __hash__(self): 
     return self._hash 

Mentre tuple sono in sola lettura solo le parti tupla delle loro sottoclassi sono di sola lettura, altre proprietà possono essere scritte come al solito, che è ciò che permette l'assegnazione a _hash indipendentemente dal fatto che si fa in __init__ o __new__. È possibile rendere la sottoclasse completamente in sola lettura impostando il valore da __slots__ a(), che ha l'ulteriore vantaggio di risparmiare memoria, ma non sarà quindi possibile assegnarlo a _hash.