Как обеспечить дополнительную инициализацию для подкласса namedtuple? - PullRequest
44 голосов
/ 02 сентября 2010

Предположим, у меня есть namedtuple, как это:

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

Я хочу реализовать для этого собственную хеш-функцию, поэтому я создаю следующий подкласс:

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

Поскольку объект является неизменным, я хочу, чтобы хеш-значение вычислялось только один раз, поэтому я делаю это:

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

    def __hash__(self):
        return self._hash

Кажется, это работает, но я действительно не уверен насчет подклассов и инициализации в Python, особенно с кортежами. Есть ли подводные камни в этом решении? Есть ли рекомендуемый способ, как это сделать? Это нормально? Заранее спасибо.

Ответы [ 3 ]

49 голосов
/ 02 сентября 2010

редактировать на 2017 год: получается namedtuple не очень хорошая идея . attrs - это современная альтернатива.

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__ - это то, что вы хотите назвать здесь, потому что кортежи неизменны.Неизменяемые объекты создаются в __new__ и затем возвращаются пользователю, а не заполняются данными в __init__.

cls необходимо дважды передать вызову super на __new__потому что __new__ по историческим / странным причинам неявно является staticmethod.

3 голосов
/ 06 января 2014

Код, о котором идет речь, может получить выгоду от супер-вызова в __init__ в случае, если он когда-либо будет разделен на подклассы в ситуации множественного наследования, но в остальном правильный.

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

Хотя кортежи доступны только для чтения, только части кортежей их подклассов доступны только для чтения, другие свойства могут записываться как обычно, что позволяет присваивать _hash независимо от того, выполняется ли это в __init__ или __new__. Вы можете сделать подкласс полностью доступным только для чтения, установив для него __slots__ в (), что дает дополнительное преимущество экономии памяти, но тогда вы не сможете назначить _hash.

0 голосов
/ 08 сентября 2018

В Python 3.7+ теперь вы можете использовать классы данных для легкого создания хеш-классов.

Код

Предположим, int типыleft и right, мы используем хеширование по умолчанию с помощью ключевого слова unsafe_hash + :

import dataclasses as dc


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


hash(Edge(1, 2))
# 3713081631934410656

Теперь мы можем использовать эти (изменяемые) объектные объекты в качестве элементов вустановить или (вводить в поле).

{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)}

Подробности

Мы можем альтернативно переопределить функцию __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

Расширяя комментарий @ ShadowRanger, пользовательская хеш-функция OP ненадежна.В частности, значения атрибутов можно поменять местами, например, hash(Edge(1, 2)) == hash(Edge(2, 1)), что, скорее всего, не то, что предназначено.

+ Обратите внимание, что имя «unsafe» предполагает использование хэша по умолчанию, несмотря на то, что объект изменчив.Это может быть нежелательно, особенно в ожидании неизменных ключей.Неизменное хеширование можно включить с помощью соответствующих ключевых слов.См. Также подробнее о логике хеширования в классах данных и о проблеме .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...