web-dev-qa-db-fra.com

Comment fournir une initialisation supplémentaire pour une sous-classe de namedtuple?

Supposons que j'ai une namedtuple comme ceci:

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

Je souhaite implémenter une fonction de hachage personnalisée pour cela, je crée donc la sous-classe suivante:

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

Puisque l'objet est immuable, je veux que la valeur de hachage ne soit calculée qu'une seule fois. Je le fais donc:

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 sous-classer et d'initialiser en Python, en particulier avec les n-uplets. Y a-t-il des pièges à cette solution? Existe-t-il un moyen recommandé de procéder? Est-ce que ça va? Merci d'avance.

44
Björn Pollex

éditer pour 2017: s'avère que namedtuple n'est pas une bonne idée . 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 car les n-uplets sont immuables. Les objets immuables sont créés dans __new__ puis renvoyés à l'utilisateur au lieu d'être renseignés avec des données dans __init__.

cls doit être passé deux fois à l'appel super de __new__ car __new__ est, pour des raisons historiques/impaires, implicitement une staticmethod.

48
habnabit

Le code de la question pourrait bénéficier d'un super appel dans le __init__ au cas où il serait un jour 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__(left, right)
        self._hash = hash(self.left) * hash(self.right)

    def __hash__(self):
        return self._hash

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

3
Gordon Wrigley

Dans Python 3.7+, vous pouvez maintenant utiliser dataclasses pour créer facilement des classes pouvant être utilisées.

Code

En supposant que les types int de left et right, nous utilisons le hachage par défaut via unsafe_hash+ mot-clé:

import dataclasses as dc


@dc.dataclass(unsafe_hash=True)
class Edge:
    left: int
    right: int


hash(Edge(1, 2))
# 3713081631934410656

Maintenant, nous pouvons utiliser ces objets (modifiables) hachables comme éléments d’un ensemble ou (clés d’un dict).

{Edge(1, 2), Edge(1, 2), Edge(2, 1), Edge(2, 3)}
# {Edge(left=1, right=2), Edge(left=2, right=1), Edge(left=2, right=3)}

Détails

Nous pouvons également remplacer la fonction __hash__:

@dc.dataclass
class Edge:
    left: int
    right: int

    def __post_init__(self):
        # Add custom hashing function here
        self._hash = hash((self.left, self.right))         # emulates default

    def __hash__(self):
        return self._hash


hash(Edge(1, 2))
# 3713081631934410656

En développant le commentaire de @ ShadowRanger, la fonction de hachage personnalisée de l'OP n'est pas fiable. En particulier, les valeurs d'attribut peuvent être échangées, par ex. hash(Edge(1, 2)) == hash(Edge(2, 1)), ce qui n'est probablement pas ce qui est prévu. 

+Notez que le nom "unsafe" suggère que le hachage par défaut sera utilisé malgré la mutabilité de l'objet. Cela peut être indésirable, en particulier lorsque vous attendez des clés immuables. Le hachage immuable peut être activé avec les mots-clés appropriés. Voir aussi plus d'informations sur logique de hachage dans les classes de données et un problème lié .

0
pylang