【问题标题】:why UserDict's __copy__ and copy()'s behavior inconsistent为什么 UserDict 的 __copy__ 和 copy() 的行为不一致
【发布时间】:2019-06-16 17:55:00
【问题描述】:

UserDict Source Code

UserDict 有 __copy__ 和 .copy() 方法。前者由 copy.copy(x) 触发,后者由 x.copy() 触发。 在 copy() 中,它首先将 self.data 设置为 {},最后使用 c.update(self) 填写 c.data。但是, update() 将触发 __setitem__ ,可能使 c.data 与 self.data 不同

from collections import UserDict
class Foo(UserDict):
    def __setitem__(self, key, value):
        if isinstance(key, int):
            self.data[key] = value
a = Foo({1:2})
# we force it to set key of str type
a.data['3'] = 4
# two different ways to copy
from copy import copy
b = copy(a)
c  = a.copy()
# this copy(a) works fine, but a.copy() is not what we expected!!!
assert b == a
assert not c == a

为什么存在这种不一致? 为什么不只是这样:

    def copy(self):
        if self.__class__ is UserDict:
            return UserDict(self.data.copy())
        import copy
        return copy.copy(self)

我不知道你可以在 Python3 中直接继承 dict。 见:How to "perfectly" override a dict? Advantages of UserDict class in Python 。 无论如何,也许没有理由再打扰UserDict了。

参见网站Trey Hunner 提到:https://treyhunner.com/2019/04/why-you-shouldnt-inherit-from-list-and-dict-in-python/

【问题讨论】:

    标签: python python-3.7


    【解决方案1】:

    UserDict.__copy__UserDict.copy() 之间的预期差异由您链接的源代码中的注释解释。来自UserDict.__copy__

    # Create a copy and avoid triggering descriptors
    

    该类的开发人员已确保__copy__ 不会触发descriptors。所以UserDict.copy() 是触发描述符的补充方法。这是通过创建空字典的副本并将所有元素重新插入该副本来实现的。

    请注意,UserDict 类是很久以前引入的,before Python 2.2,作为对字典进行子类化的一种方式。当您实现一个旨在由具有不可预测技能水平的开发人员子类化的类时,您要采取预防措施,让类实例以合理的方式运行,即使子类编写得不好。将副本创建为空字典并重新插入元素对我来说看起来像是一种安全的编程。

    另一种方法可能是按照您的建议从.copy() 调用.__copy__(),并在文档中写入子类应覆盖.copy() 以触发描述符。但是有多少子类的开发者会阅读这部分文档,理解它的含义,花时间覆盖该方法,并且不会在他们的实现中引入错误?

    【讨论】:

    • 另一个问题:你说它是很久以前介绍的。我应该继续使用它吗?或者另辟蹊径。比如How to “perfectly” override a dict?,它继承自 collections.MutableMapping
    • 根据我链接为“在 Python 2.2 之前”的答案,它已经变得非常多余。如果需要,您可以对内置 dict 进行子类化。这就是我所知道的一切:-)
    • @mizuku 只是想补充一点,在做出此决定或 UserDict 与 dict 继承时,请注意从 dict 继承可能有一些主要缺点:treyhunner.com/2019/04/…
    【解决方案2】:

    有人可能会从 UserDict 派生一个类并覆盖 update 以在插入新元素时执行一些特殊操作。使用您的替代实现,副本上不会发生特殊情况。

    【讨论】:

    • 但是 b 和 c 都有从 a 复制的 _setitem_ 方法。您可以在复制完成后更新。而更重要的是为什么copy.copy()和.copy()的结果不一样。如果你想在复制时发生一些特别的事情。那么你应该让 copy.copy 和 .copy() 都调用 .update 方法,这在 _copy_ 中不是这样
    • @mizuku 我开始编辑这个答案,但后来意识到我不会保留一个句子:-) 所以我写了第二个答案。感谢您提出这个问题,它促成了一些有趣的研究。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-05
    • 2014-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多