2010-09-02 22 views
39

Supongamos que tengo un namedtuple así:¿Cómo proporcionar una inicialización adicional para una subclase de namedtuple?

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

Quiero aplicar un hash-función personalizada para esto, así que crear los siguientes subclases:

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

Puesto que el objeto es inmutable, lo quieren el hash-valor que se calcula sólo una vez, así que hacer esto:

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

    def __hash__(self): 
     return self._hash 

esto parece estar funcionando, pero realmente no estoy seguro de s ubclassing e inicialización en Python, especialmente con tuplas. ¿Hay alguna trampa para esta solución? ¿Hay alguna manera recomendada de cómo hacer esto? ¿Está bien? Gracias por adelantado.

+1

Las pruebas han demostrado que no vale la pena almacenar en caché el valor hash, consulte [número 9685] (https://mail.python.org/pipermail/python-bugs-list/2013- enero/193636.html) –

Respuesta

44

edición para 2017:turns out namedtuple isn't a great idea. attrs es la 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__ es lo que desea llamar aquí porque tuplas son inmutables. Los objetos inmutables se crean en __new__ y luego se devuelven al usuario, en lugar de rellenarse con datos en __init__.

cls tiene que pasar dos veces a la llamada super en __new____new__ porque es, por razones históricas/impares implícitamente una staticmethod.

+3

¿Por qué debería preferir esto a mi solución (esto no pretende ser una pregunta cínica, realmente quiero entender si hay diferencias significativas)? –

+9

Su solución no usa 'super', y por lo tanto se romperá en cualquier tipo de situación de herencia múltiple. No importa si * usted * no usa MI; si alguien más lo hace, su código se romperá horriblemente. No es difícil evitar este problema simplemente usando 'super' en todas partes. – habnabit

+5

También es beneficioso agregar '__slots__ =()' a la clase. De lo contrario, se creará '__dict__', anulando la eficacia de memoria de' namedtuple'. – WGH

3

El código en la pregunta podría beneficiarse de una súper llamada en el __init__ en caso de que alguna vez se subclasifique en una situación de herencia múltiple, pero de lo contrario es correcto.

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 

Mientras se tuplas de sólo lectura solamente las partes tupla de sus subclases son de sólo lectura, otras propiedades pueden ser escritos como de costumbre, que es lo que permite la asignación a _hash independientemente de si se hace en __init__ o __new__. Puede hacer que la subclase sea completamente de solo lectura configurando su __slots__ en(), lo que tiene el beneficio adicional de guardar memoria, pero luego no podrá asignar a _hash.

Cuestiones relacionadas