4

UserDict Source Code

UserDict has __copy__ and .copy() methods.The former is triggered by copy.copy(x) and the latter is by x.copy(). in copy(), it first set self.data to {} and in the end uses c.update(self) to fill out c.data. BUT, update() will trigger __setitem__ , possibly making c.data differs from 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

why this inconsistence exists? why not just this:

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

I didn't know you can subclass dict directly in Python3. See : How to "perfectly" override a dict? Advantages of UserDict class in Python . Anyway MAYBE there is no reason to bother with UserDict anymore.

See the website Trey Hunner mentioned : https://treyhunner.com/2019/04/why-you-shouldnt-inherit-from-list-and-dict-in-python/

mizuku
  • 43
  • 8

2 Answers2

1

The intended difference between UserDict.__copy__ and UserDict.copy() is explained by a comment in the source code you linked. From UserDict.__copy__:

# Create a copy and avoid triggering descriptors

The developers of the class have made sure that __copy__ does NOT trigger descriptors. So UserDict.copy() is the complementary method which DOES trigger descriptors. This is achieved by creating a copy of the empty dictionary, and re-inserting all elements into that copy.

Note that the UserDict class was introduced long ago, before Python 2.2, as a means for subclassing dictionaries. When you implement a class that is meant to be subclassed by developers of unpredictable skill levels, you take precautions to let the class instances behave in a reasonable way even when subclasses are not well written. Creating the copy as an empty dictionary and re-inserting the elements looks like that kind of safe programming to me.

An alternative might have been to call .__copy__() from .copy() as in your suggestion, and write into the documentation that subclasses should overwrite .copy() to trigger descriptors. But how many developers of subclasses would have read that part of the documentation, understood what it meant, taken the time to overwrite the method, and not introduced a bug in their implementation?

Roland Weber
  • 3,395
  • 12
  • 26
  • Another queston: you say it was introduced long ago. Should I keep using it? Or find another way around. Like [How to “perfectly” override a dict?](https://stackoverflow.com/questions/3387691/how-to-perfectly-override-a-dict) which inherits from collections.MutableMapping – mizuku Jun 20 '19 at 16:49
  • According to the answer I linked as "before Python 2.2", it has become mostly redundant. You can subclass the built-in dict if you need to. That's all I know about it :-) – Roland Weber Jun 21 '19 at 08:41
  • 1
    @mizuku just wanted to add that when making this decision or UserDict vs dict inheritance, note that inheriting from dict can have some major downsides: https://treyhunner.com/2019/04/why-you-shouldnt-inherit-from-list-and-dict-in-python/ – Trey Hunner Jun 23 '19 at 20:07
0

Somebody might derive a class from UserDict and overwrite update to do something special whenever a new element is inserted. With your alternative implementation, something special would not happen on the copy.

Roland Weber
  • 3,395
  • 12
  • 26
  • But both b and c have \__setitem__ method copied from a. You can update after the copy is done. And what is more important is that why copy.copy() and .copy()'s results are not the same. If you want something special happen when copied. Then you should have copy.copy and .copy() both invoke .update method which is not the case in \___copy___ – mizuku Jun 17 '19 at 05:49
  • @mizuku I started to edit this answer, but then realized that I wouldn't have kept a single sentence of it :-) So I wrote a second answer instead. Thanks for asking the question, it made for some interesting research. – Roland Weber Jun 20 '19 at 04:55