2010-09-02 7 views
39

Supposons que j'ai un namedtuple comme ceci:Comment fournir une initialisation supplémentaire pour une sous-classe de namedtuple?

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

Je veux mettre en œuvre une fonction de hachage personnalisé pour cela, donc je crée la sous-classe suivante:

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

Puisque l'objet est immuable, je veulent la valeur de hachage à calculer une seule fois, donc je fais ceci:

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

    def __hash__(self): 
     return self._hash 

cela semble fonctionner, mais je ne suis vraiment pas sûr de ubclassing et initialisation en Python, en particulier avec les tuples. Y a-t-il des pièges à cette solution? Y a-t-il une manière recommandée comment faire ceci? Est-ce bien? Merci d'avance.

+1

Les tests ont montré qu'il ne vaut pas la peine de mettre en cache la valeur de hachage, voir [numéro de dossier 9685] (https://mail.python.org/pipermail/python-bugs-list/2013-janvier/192363.html) –

Répondre

44

modifier pour 2017:turns out namedtuple isn't a great idea. attrs est l'alternative moderne.

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__ est ce que vous voulez appeler ici parce que tuples sont immuables. Les objets immuables sont créés dans __new__, puis renvoyés à l'utilisateur au lieu d'être remplis avec les données __init__.

cls doit être passé deux fois à l'appel super sur __new__ parce __new__ est, pour des raisons historiques/impair implicitement un staticmethod.

+3

Pourquoi devrais-je préférer ceci à ma solution (ce n'est pas censé être une question cynique, je veux vraiment comprendre s'il y a des différences significatives)? –

+9

Votre solution n'utilise pas 'super', et donc va se casser dans n'importe quelle sorte de situation d'héritage multiple. Cela n'a pas d'importance si * vous * n'utilisez pas MI; si quelqu'un d'autre le fait, leur code se cassera horriblement. Il n'est pas difficile d'éviter ce problème en utilisant simplement «super» partout. – habnabit

+5

Il est également utile d'ajouter '__slots__ =()' à la classe. Autrement '__dict__' sera créé, annulant l'efficacité de la mémoire de' namedtuple'. – WGH

3

Le code de la question pourrait bénéficier d'un appel super dans le __init__ au cas où il serait jamais sous-classé dans une situation d'héritage multiple, mais sinon est correct.

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 

Alors que tuples sont en lecture seule que les parties tuple de leurs sous-classes sont en lecture seule, d'autres propriétés peuvent être écrites comme d'habitude, qui est ce qui permet l'attribution de _hash, peu importe que ce soit fait dans __init__ ou __new__. Vous pouvez rendre la sous-classe entièrement en lecture seule en définissant __slots__ à(), ce qui présente l'avantage supplémentaire d'économiser de la mémoire, mais vous ne pourrez pas l'affecter à _hash.