【问题标题】:Hashable subclasses of mutable classes in PythonPython中可变类的可散列子类
【发布时间】:2021-09-30 18:51:22
【问题描述】:

我想构建一个类的子类,它基本上是一个列表/字典的包装器,我想使用这个类的实例作为键。为了方便访问列表/字典方法,我想我可以只对它们进行子类化,但后来我失去了哈希性。

我知道这与可变类的__eq__ 不兼容(我不需要它),所以我想出了以下解决方案。

class Foo(list):
    __hash__ = object.__hash__
    __eq__ = object.__eq__

虽然我怀疑这会出现在我的特定应用程序中,但我想如果另一个类(具有自己的 __hash__)会出现在子类的 MRO 中的 Foolist 之间,这可能会导致问题。

  1. 有没有更 Pythonic 的方式来做到这一点?我可以只添加一个列表/字典作为属性,然后简单地使用该属性(这会使其他地方的代码更麻烦)或我需要的列表/字典方法(但如果我需要的不止一对,那就更麻烦了方法)。
  2. 是否有一些模式/配方可以让我在合作环境中做这种事情(换句话说,如果我确实预期Foolist 之间的另一个类,我应该如何更改代码在 MRO 中)?

【问题讨论】:

  • 相反,我将包装 dict 类,并覆盖 set / get 函数以获取列表并以预定义的方式处理它们
  • @FloLie:是的,我想过这个问题,但是如果我想拥有更多方法,似乎需要编写很多代码,而且我可能会,比如__iter____len__
  • 如果你实现__getattr__,你可以非常简单地编写包装类(使用组合)
  • @juanpa.arrivillaga:你有什么想法?我可以看到__getitem__+__setitem__ 可以如何使用(但是您仍然有很多其他方法...),但是__getattr__ 将如何帮助?

标签: python inheritance design-patterns subclass


【解决方案1】:

我决定根据我的评论提供一个答案,如果IFs 很多的话

如前所述,我宁愿更改 dict 的实现,以 list 作为键。然而,这确实带来了一些问题,正如其他人所提到的,这就是 lists 不应该成为密钥的原因。

一点解释:

查看可变变量时,它有两个属性,可以通过以下方式进行比较:

  1. 它的identity
  2. 它的content

身份 当一个变量被创建时,例如a=[1,2,3],它被分配了一个可以用id(a)调用的内存位置。即使在修改了 a 之后,此 id 也会标识 a。但是,在比较 ids 时,使用第二个变量 b=[1,2,3],我们得到 id(a) == id(b) = False

内容 我们还可以通过将变量转换为字符串以非常简单的方式查看变量的内容,例如上面的示例str(a) == str(b) = True。但是,如果我们这样做

a = [1,2,3]
str_a = str(a)
a.append(4)
str_new_a = str(a)

我们得到str_a == str_new_a = False,这在应用程序作为dict中的键的情况下是有问题的,因为在保存值的那一刻,我们拍摄a的“快照”,然后如果我们通过@查找987654337@修改后不匹配。

因此,您必须决定在您的情况下 content 还是 identity 是相关的查找标准。

下面提供了一个示例实现,其中 hashableKey1 作为 ID 匹配,hashableKey2 作为内容匹配。

from collections import Hashable

class CoolDict(dict):
    hashableKeyType = 1
    def __setitem__(self, name, value):
        print(name, value)
        if isinstance(name, Hashable):
            super().__setitem__(name, value)
        else:
            super(CoolDict, self).__setitem__(self.hashableKey(name), value)
    def __getitem__(self, name):
        if isinstance(name, Hashable):
            return super().__getitem__(name)     
        else:
            return super().__getitem__(self.hashableKey(name))   
    
    def hashableKey(self, name):
        if self.hashableKeyType == 1:
            return self.hashableKey1(name)
        elif self.hashableKeyType == 2:
            return self.hashableKey2(name)
    def hashableKey1(self, name):
        return id(name)
            
    def hashableKey2(self, name):
        return str(name)
    
coolDict  = CoolDict()

### hashableKey1:

#Working
key = [1,2,3]
coolDict[key] = 123
print(coolDict[key])

#Not Working
coolDict[[2,3,4]] = 234
print(coolDict[[2,3,4]])

### hashableKey2:
coolDict.hashableKeyType = 2

#Working
coolDict[[2,3,4]] = 234
print(coolDict[[2,3,4]])


#Not Working
key = [1,2,3]
coolDict[key] = 123
key.append(4)
print(key)
print(coolDict[key])

【讨论】:

  • 这是一个有趣的想法,我想我有点误解了你的评论。不过,出于您提到的原因,我不确定第二种方法。 (我想我在 SO 的某个地方看到过类似的实现,但有同样的警告。)你为什么认为这个解决方案比我的更好? (另外,恕我直言,三个 if 并不是很多,特别是如果它们在单独的方法中并且没有嵌套。:))
  • 这可能只是我的思考过程。如果我可以改变一个容器处理物品的行为,我就不必改变一个物品的行为来由无关的第三方处理,这有意义吗?你的问题不是列表,它工作正常,什么不起作用是字典,因为它“不”吃你喂它的东西。对于ifs,我不是指代码ifs,但是这两种方式中的任何一种都有很多限制并且在我看来是有风险的,因为有时你会错误地更改身份(例如复制、子集等)。涉及到嵌套项目,您需要考虑是否会影响
  • 我明白了。好吧,我的意思是,我想要一个类,虽然 功能上 是一个带有一些额外绒毛的列表/字典包装器,但 抽象地 表示一个具体对象(例如一副纸牌) , 或 ECS 中的实体),其身份比裸列表/字典/集合/其他可变类更有意义。重点是不能将列表/字典放入容器中,而是拥有一个具有(几乎)所有列表/字典功能的类,它也可以放入容器中。
猜你喜欢
  • 1970-01-01
  • 2013-03-30
  • 1970-01-01
  • 2022-01-11
  • 2021-07-10
  • 2012-05-02
  • 2018-12-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多