如何覆盖 "set" 内置?

How to override "set" builtin?

我想实现以下功能:

  1. TestClass values 接受任意数量的 NewClass 个对象
  2. 只有 NewClass 个不具有所有相同属性值的对象被添加到 TestClass.values

我想到了这个:

class NewClass:

    def __init__(self, value1, value2):
        self.value1 = value1
        self.value2 = value2


class TestClass:

    def __init__(self, *values):
        self.values = self._set(values)

    def _set(self, object_list):
        unique_dict = {}
        for obj in object_list:
            if list(obj.__dict__.values()) not in unique_dict.values():
                unique_dict[obj] = list(obj.__dict__.values())
        return list(unique_dict.keys())


obj1 = NewClass(1, 2)
obj2 = NewClass(1, 2)
obj3 = NewClass(5, 2)

test = TestClass(obj1, obj2, obj3)

只有 obj1obj3test.values

我想知道如何以“协议”方式做到这一点,例如 lenadd

def __len__(self):
    return len(self.values)

与第一种方法相比,第二种方法是否具有有意义的好处?

如果您在 NewClass 上定义 __hash____eq__,您可以将实例传递给 set(),它将使用此函数来确定对象是否相等集的条款。您需要小心可变实例,因为属性可能会在事后发生变化。

这是一个简单的例子:

class NewClass:
    def __init__(self, value1, value2):
        self.value1 = value1
        self.value2 = value2
    def __hash__(self):
        # take the hash of the tuple
        return hash((self.value1, self.value2))
    def __eq__(self,other):
        # are the tuples equal?
        return (self.value1, self.value2) == (other.value1, other.value2)

    def __repr__(self):
        return f'NewClass({self.value1}, {self.value2})'

class TestClass:
    def __init__(self, *values):
        self.values = list(set(values))


obj1 = NewClass(1, 2)
obj2 = NewClass(1, 2)
obj3 = NewClass(5, 2)

test = TestClass(obj1, obj2, obj3)

test.values
# Only the different instances:
# [NewClass(1, 2), NewClass(5, 2)]

假设你的 value1value2 是不可变的(整数、字符串和元组是好的;列表和字典不是),你可以散列它们——实现 __hash____eq__ 将允许内置集类型识别重复项。

class NewClass:
    def __init__(self, value1, value2):
        self.value1 = value1
        self.value2 = value2
    def __hash__(self):
        return hash((self.value1, self.value2))
    def __eq__(self, other):
        return self.value1 == other.value1 and self.value2 == other.value2
    def __repr__(self):
        return 'NewClass(%r, %r)' % (self.value1, self.value2)

print(set([NewClass(1,2), NewClass(1,2), NewClass(3,4)]))

...正确地 returns:

{NewClass(1, 2), NewClass(3, 4)}

只需添加这两个答案...使用冻结数据class 可以避免很多样板文件。它不仅为您生成 __hash____eq____repr__,而且还在对象的生命周期内强制执行不变性。

编写 __hash____eq__ 在概念上并不难,但众所周知,它们很容易出错。更新 class 定义,例如添加或删除属性、更改属性数据类型等,可以为 class 属性和散列方法之间的差异留出空间。

这个问题对我来说是使用dataclasses的最大动力。您可以创建简洁、简单的不可变类型,您可以轻松对其进行哈希处理。您将列出或比较属性的繁琐工作留给了 dataclass 包装器,而只需要使用更易读的 class.

格式
from dataclasses import dataclass

@dataclass(frozen=True)
class NewClass:
    value1: int
    value2: int

obj1 = NewClass(1, 2)
obj2 = NewClass(1, 2)
obj3 = NewClass(5, 2)

test = {obj1, obj2, obj3}
print(test)
{NewClass(value1=1, value2=2), NewClass(value1=5, value2=2)}