[New-bugs-announce] [issue43246] Dict copy optimization violates subclass invariant

Joshua Bronson report at bugs.python.org
Wed Feb 17 17:12:32 EST 2021


New submission from Joshua Bronson <jabronson at gmail.com>:

If I understand correctly, it should be an invariant that in the code below, for all "Parent" classes, for all "method"s, Child1.method should return the same result as Child2.method:

```
class Parent:
    def __init__(self, value):
        self._value = value

    def method(self):
        return self._value


class Child1(Parent):
    pass


c1 = Child1(42)
result = c1.method()
assert result == 42, result


class Child2(Parent):
    def method(self):
        return super().method()


c2 = Child2(42)
result = c2.method()
assert result == 42, result
```

But when "Parent" is "dict" and method is "__iter__", that is not the case:

```
SHOW_BUG = True

class ChildDict1(dict):
    """Simplification of werkzeug.datastructures.MultiDict."""
    def __init__(self):
        pass
    
    if not SHOW_BUG:
        def __iter__(self):
            return super().__iter__()

    def add(self, key, value):
        self.setdefault(key, []).append(value)
    
    def __setitem__(self, key, value):
        """Like add, but removes any existing key first."""
        super().__setitem__(key, [value])
    
    def getall(self, key) -> list:
        return super().__getitem__(key)

    def __getitem__(self, key):
        """Return the first value for this key."""
        return self.getall(key)[0]

    def items(self, multi=False):
        for (key, values) in super().items():
            if multi:
                yield from ((key, value) for value in values)
            else:
                yield key, values[0]
    
    def values(self):
        return (values[0] for values in super().values())
    
    # Remaining overridden implementations of methods
    # inherited from dict are elided for brevity.


cd1 = ChildDict1()
assert dict(cd1) == {}
cd1[1] = "one"
cd1.add(1, "uno")
assert cd1.getall(1) == ["one", "uno"]
assert list(cd1.items()) == [(1, "one")]
assert list(cd1.values()) == [ "one"]
assert dict(cd1) == {1: "one"}, cd1  # XXX
```

If you run the above as-is, the line marked "XXX" will trigger an AssertionError demonstrating the unexpected behavior. If you change SHOW_BUG to False, it won’t.

Is it intended that toggling the value of SHOW_BUG in this code causes different results?

You can visit https://repl.it/@jab/dict-subclass-copy-surprise to run the examples above directly in your browser.

----------
messages: 387191
nosy: jab
priority: normal
severity: normal
status: open
title: Dict copy optimization violates subclass invariant
type: behavior

_______________________________________
Python tracker <report at bugs.python.org>
<https://bugs.python.org/issue43246>
_______________________________________


More information about the New-bugs-announce mailing list