【问题标题】:Python's `dict` doesn't recognize its own keyPython 的 `dict` 无法识别自己的密钥
【发布时间】:2022-01-02 18:01:50
【问题描述】:

编辑 2:我终于能够制作出 MWE:

from typing import Generic, TypeVar
T = TypeVar('T')

class Cache:
    __dict = {}

    @classmethod
    def add(cls, item):
        cls.__dict[item] = (item, [item, item, item, {item: item}])
        print('On setting:', item in cls.__dict)

    def __init_subclass__(cls, **kwargs):
        Cache.add(cls)


class Class(Cache, Generic[T]):
    pass

d = Cache._Cache__dict
tp = list(d)[0]
print('On checking:', tp in d)

在 python 3.6 中,输出为:

On setting: True
On checking: False

在 3.8 中是:

On setting: True
On checking: True

如果这还不够好奇,如果我从Generic[T] 中删除继承,那么一切正常。

原创

我正在使用 Python 3.6,当我尝试从字典中获取密钥时得到 KeyError

# d: Dict[type, Any]

tp = list(d.keys())[0]
d[tp]
# KeyError: ...

意思是从字典中取出的键会导致这个异常。请注意,d 只有一个条目。 key-type 是一个以GenericMeta 作为元类的类型对象,所以这可能是问题所在吗?

我使用调试器验证了以下属性:

  1. id(tp) 多次调用都一样。
  2. hash(tp) 多次调用都一样。
  3. tp is list(d.keys())[0]
  4. tp == list(d.keys())[0]
  5. len(d) == 1

编辑:

  1. print(type(tp)) # <class 'typing.GenericMeta'>
  2. print(type(tp)) # <class 'dict'>

我的问题是:这种行为的原因可能是什么?

由于某些包的兼容性问题,我无法更新python版本,所以请不要告诉我更新,除非它是一个已知的错误,在以后的版本中得到解决。

【问题讨论】:

  • 请显示一个实际的minimal reproducible example。我不知道你的代码实际上在做什么,所以在我这样做之前,我不得不关闭你的问题,因为它不可重现
  • 停止提供可能发生的事情的提示。显示一个我们可以重新创建的实际 dict 定义,d[list(d.keys())[0]] 会为其引发 KeyError
  • 是的,你可以。从真正的字典开始。删除一个键并检查它是否仍然产生错误。如果是,请移除另一个密钥。如果不是,你刚刚删除的key就是问题所在,你可以识别出来。
  • 可在 Python 3.6 中重现,而不是在 Python 3.7 中;我的猜测是这是一个已修复的错误。那个错误可能是什么是一个谜。我可以确认 cls.__dict is Cache._Cache__dict 即使在 Python 3.6 中也是如此。
  • 制作 mcve 做得很好!这从看似无稽之谈变成了有趣的历史蟒蛇奥秘

标签: python dictionary python-3.6


【解决方案1】:

这是一个初始化顺序问题。

在 Python 3.6 上,Classtyping.GenericMeta 的一个实例。 typing.GenericMeta__new__ 中执行重要的初始化,但是该初始化只能在 type.__new__ 返回要初始化的内容时开始。 type.__new__ 负责调用__init_subclass__,因此您的__init_subclass__ 在任何GenericMeta 初始化发生之前运行。

当您的__init_subclass__Class 添加到字典时,尚未执行==hash 正常工作所需的初始化。此操作最终使用无效哈希。稍后,一旦初始化完成,查找使用正确的哈希,找不到Class

在后来的 Python 版本中,整个泛型类的实现完全改变了。 typing.GenericMeta 不再存在。

【讨论】:

  • 太棒了。感谢您的见解。很有意思!无论如何,这大致就是我认为会发生的情况。我想我要么必须升级运行时版本,要么让缓存变得懒惰(不是在__init_subclass__ 上,而是在第一次使用时)。可能会导致多处理的开销,但在一般情况下也可能更有效。
  • 无论如何,总结一下 - 用于存储对象的哈希值在插入和提取之间发生了变化(确实是 python 中的一个错误!)。还要注意以下有趣的问题,dict(d)(也不是set(d))不会重新计算内部对象的哈希值(因此它通常比最初考虑的更有效)。要重新计算这些值,请使用 dict(d.items())
猜你喜欢
  • 1970-01-01
  • 2018-03-01
  • 1970-01-01
  • 2022-01-14
  • 1970-01-01
  • 2016-06-18
  • 2017-03-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多