Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The result of calling dict.* methods on OrderedDict is undefined. #68909

Closed
ericsnowcurrently opened this issue Jul 25, 2015 · 5 comments
Closed
Assignees
Labels
docs Documentation in the Doc dir type-bug An unexpected behavior, bug, or error

Comments

@ericsnowcurrently
Copy link
Member

BPO 24721
Nosy @rhettinger, @markshannon, @ericsnowcurrently

Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

Show more details

GitHub fields:

assignee = 'https://github.com/rhettinger'
closed_at = <Date 2015-07-25.20:33:30.953>
created_at = <Date 2015-07-25.17:10:58.383>
labels = ['type-bug', 'invalid', 'docs']
title = 'The result of calling dict.* methods on OrderedDict is undefined.'
updated_at = <Date 2015-07-26.10:41:18.347>
user = 'https://github.com/ericsnowcurrently'

bugs.python.org fields:

activity = <Date 2015-07-26.10:41:18.347>
actor = 'Mark.Shannon'
assignee = 'rhettinger'
closed = True
closed_date = <Date 2015-07-25.20:33:30.953>
closer = 'rhettinger'
components = ['Documentation']
creation = <Date 2015-07-25.17:10:58.383>
creator = 'eric.snow'
dependencies = []
files = []
hgrepos = []
issue_num = 24721
keywords = []
message_count = 5.0
messages = ['247354', '247358', '247360', '247361', '247423']
nosy_count = 4.0
nosy_names = ['rhettinger', 'docs@python', 'Mark.Shannon', 'eric.snow']
pr_nums = []
priority = 'low'
resolution = 'not a bug'
stage = None
status = 'closed'
superseder = None
type = 'behavior'
url = 'https://bugs.python.org/issue24721'
versions = ['Python 2.7', 'Python 3.4', 'Python 3.5', 'Python 3.6']

@ericsnowcurrently
Copy link
Member Author

(see bpo-24667)

collections.OrderedDict subclasses dict so calling dict's methods on an OrderedDict works. However, neither the pure Python nor the C implementation of OrderedDict was written to support doing so. In fact, both of them currently enter an inconsistent state when this happens. For example:

    # Python 3.4 (pure Python implementation)
    >>> from collections import OrderedDict
    >>> od = OrderedDict([('spam', 1), ('eggs', 2)])
    >>> dict.__delitem__(od, 'spam')
    >>> str(od)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/usr/lib/python3.4/reprlib.py", line 24, in wrapper
        result = user_function(self)
      File "/usr/lib/python3.4/collections/__init__.py", line 198, in __repr__
        return '%s(%r)' % (self.__class__.__name__, list(self.items()))
      File "/usr/lib/python3.4/_collections_abc.py", line 485, in __iter__
        yield (key, self._mapping[key])
    KeyError: 'spam'

    # Python 3.5 (C implementation)
    >>> from collections import OrderedDict
    >>> od = OrderedDict([('spam', 1), ('eggs', 2)])
    >>> dict.__delitem__(od, 'spam')
    >>> str(od)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    KeyError: 'spam'

This is a consequence of subclassing a builtin type, which typically do not have good support for subclassing (e.g. bpo-10977).

It probably isn't worth making any changes to the code of either OrderedDict implementations. At most I'd recommend a note in the OrderedDict documentation indicating that the results of passing an OrderedDict object to dict.* methods are undefined.

@ericsnowcurrently ericsnowcurrently added docs Documentation in the Doc dir type-bug An unexpected behavior, bug, or error labels Jul 25, 2015
@rhettinger rhettinger assigned rhettinger and unassigned docspython Jul 25, 2015
@rhettinger
Copy link
Contributor

This is a consequence of subclassing a builtin type

Not really. This is how subclassing works in general. Any time you a user calls a parent class directly on an instance of subclass, they are bypassing whatever the subclass needs to do to maintain its invariants.

class A:
    def __init__(self):
        self.data = []
    def add(self, x):
        self.data.append(x)

class B(A):
    'Track the number of odds'
    def __init__(self):
        A.__init__(self)
        self.odds = 0
    def add(self, x):
        A.add(self, x)
        self.odds += (x % 2)

b = B()
b.add(1)
b.add(2)
b.add(3)
b.add(4)
A.add(b, 5)
assert b.odds == sum(x%1 for x in b.data), 'OMG, B is broken!'

There is nothing special about OrderedDicts in this regard. Perhaps there should be a FAQ entry regarding the "facts of life" in the world of object oriented programming.

@ericsnowcurrently
Copy link
Member Author

Ah, you're right. I was hung up on bpo-10977. :)

@ericsnowcurrently
Copy link
Member Author

Feel free to close this, Raymond.

@markshannon
Copy link
Member

I think this is a bug. This is not a normal case of subclassing as the interpreter calls the C API PyDict_XXX() in many cases where a dictionary subclass is passed in.

For example:
class C: pass
c = C()
# Liskov substitution principle says this is OK.
c.__dict__ = OrderedDict()
c.a = 1

All access to the ordered dict is via the dict.__setitem__ method.

I think this should be documented.

@ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs Documentation in the Doc dir type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

3 participants