为什么UserDict的__copy__和copy()的行为不一致

时间:2019-06-16 17:55:00

标签: python python-3.7

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 Hunnerhttps://treyhunner.com/2019/04/why-you-shouldnt-inherit-from-list-and-dict-in-python/

2 个答案:

答案 0 :(得分:1)

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

# Create a copy and avoid triggering descriptors

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

请注意,UserDict类是很早以前引入的before Python 2.2,作为子类化字典的一种方式。当实现要由技能水平无法预测的开发人员子类化的类时,请采取预防措施,以使即使没有正确编写子类,类实例也可以以合理的方式运行。对我来说,将副本创建为空字典并重新插入元素似乎是一种安全的编程。

另一种选择是按照您的建议从.__copy__()调用.copy(),然后写入文档中,子类应覆盖.copy()来触发描述符。但是,有多少子类的开发人员会阅读文档的那部分内容,理解其含义,花时间覆盖该方法,而不会在实现中引入错误?

答案 1 :(得分:0)

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