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

bpo-34320: Fix dict(o) didn't copy order of dict subclass #8624

Merged
merged 5 commits into from
Sep 26, 2018

Conversation

methane
Copy link
Member

@methane methane commented Aug 2, 2018

When dict subclass overrides order (__iter__(), keys(), and items()), dict(o)
should use it instead of dict ordering.

https://bugs.python.org/issue34320

Copy link
Member

@serhiy-storchaka serhiy-storchaka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to add tests for PEP 468 and PEP 520. These tests should be backported to 3.6, but the test for the dict constructor can not.

@@ -2379,7 +2379,7 @@ dict_merge(PyObject *a, PyObject *b, int override)
return -1;
}
mp = (PyDictObject*)a;
if (PyDict_Check(b)) {
if (PyDict_CheckExact(b)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be too restrictive. Most dict subclasses don't change the order. Maybe check also that __iter__ is not overridden?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean, "Maybe check instead", right? So something like the following.

if (PyDict_Check(b) && Py_TYPE(b)->tp_iter == PyDict_Type.tp_iter) {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the tp_iter checks seems like the most reasonable way to go. That would preserve the fast conversion speed of defaultdict.

@rhettinger rhettinger self-assigned this Aug 2, 2018
@serhiy-storchaka
Copy link
Member

Please add tests.

@methane
Copy link
Member Author

methane commented Aug 3, 2018

@serhiy-storchaka Which type of test?
New test_dict_copy_order is minimal test for the regression; it fails on master branch.

@serhiy-storchaka
Copy link
Member

Tests for for PEP 468 and PEP 520. test_dict_copy_order can't be backported to 3.6.

# Dict preserves order by language spec from 3.7.
# Dict preserves order by implementation from 3.6.
if sys.version_info < (3, 7):
test_dict_copy_order = support.cpython_only(test_dict_copy_order)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 3.7+ this is not needed. And I think that in <3.7 this test should be backported.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, what do you mean concretely?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests are ran with concrete Python version. sys.version_info is a constant in every branch. In branches master and 3.7 these two lines should be removed. In branch 3.6 the decorator should be added unconditionally. But the backport to 3.6 needs editing in any case (only the test should be backported), so I don't think this will add additional work.

@serhiy-storchaka
Copy link
Member

Perhaps there are better places for tests for PEP 468 and PEP 520. I would try to break the order of all kwargs dicts and class dicts and see what tests will fail.

@serhiy-storchaka
Copy link
Member

I have made dict_merge() reverting the order for all mappings. The only failed tests are test.test_collections.TestChainMap.test_order_preservation, test.test_dict.DictTest.test_merge_and_mutate and several tests in test_ordered_dict. Nothing specific for PEP 468 and PEP 520. @ericsnowcurrently, do you know good places for testing these PEPs?

@methane
Copy link
Member Author

methane commented Aug 6, 2018

FYI, PEP 520 is reverted because of dict preserves order.
type.__new__ should preserve order of passed namespace mapping. But it's not PEP 520.
It's just an expected behavior of "dict preserves insertion order".

@methane methane closed this Sep 20, 2018
@methane methane reopened this Sep 20, 2018
@methane
Copy link
Member Author

methane commented Sep 20, 2018

We missed 3.7.1, maybe.
I want to merge this simple bugfix without waiting long time to determine where we
should put tests for PEP 468 and 520.

May I add comment like "# TODO: Move this test to better place" and merge this?
@serhiy-storchaka

@serhiy-storchaka
Copy link
Member

Would be nice to mention PEP 468 and 520 in comments for corresponding tests.

Copy link
Member

@serhiy-storchaka serhiy-storchaka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @methane!

Copy link
Member

@ericsnowcurrently ericsnowcurrently left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aside from moving the tests you already have, there should be at least 3 more tests:

  • with a metaclass.__prepare__ that returns an OrderedDict (see https://bugs.python.org/issue34320#msg325898) -- this would be next to either other tests related to __prepare__() or other tests related to type()
  • dict(obj) where obj is an instance of some non-OrderedDict subclass of dict without a custom __iter__ -- this would be in test_dict.py
  • dict(obj) where obj is an instance of some subclass of dict with a custom __iter__ -- this would be in test_dict.py

@@ -650,6 +650,41 @@ def test_free_after_iterating(self):
support.check_free_after_iterating(self, lambda d: iter(d.values()), self.OrderedDict)
support.check_free_after_iterating(self, lambda d: iter(d.items()), self.OrderedDict)

# TODO: This test is relating to PEP 468.
# Move this test to somewhere more appropriate.
def test_kwargs_order(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is about kwargs ordering rather than OrderedDict, so I'd expect it to be next to other tests for kwargs, not here in test_ordered_dict.py.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but problem is I can't find other tests for kwargs.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added new test class for this, in test_call.py

self.assertIsInstance(res, dict)
self.assertEqual(list(res.items()), expected)

# TODO: This test is relating to PEP 520.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is not specific to PEP 520, which only relates directly to the class definition namespace. So you should drop this comment.


# TODO: This test is relating to PEP 520.
# Move this test to somewhere more appropriate.
def test_type_namespace_order(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test should be next to other tests of the type() builtin.

C = type('C', (), od)
self.assertEqual(list(C.__dict__.items())[:2], [('b', 2), ('a', 1)])

def test_dict_copy_order(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test should be in test_dict.py.

@bedevere-bot
Copy link

When you're done making the requested changes, leave the comment: I have made the requested changes; please review again.

@serhiy-storchaka
Copy link
Member

Thank you for your suggestions @ericsnowcurrently. They all LGTM.

@methane methane force-pushed the dict-copy-order branch 3 times, most recently from f117479 to 603bc6c Compare September 25, 2018 14:27
@methane methane changed the title bpo-34320: Fix dict(od) didn't copy order bpo-34320: Fix dict(o) didn't copy order of dict subclass Sep 26, 2018
@miss-islington miss-islington merged commit 2aaf98c into python:master Sep 26, 2018
@miss-islington
Copy link
Contributor

Thanks @methane for the PR 🌮🎉.. I'm working now to backport this PR to: 3.6, 3.7.
🐍🍒⛏🤖

@bedevere-bot
Copy link

GH-9582 is a backport of this pull request to the 3.7 branch.

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Sep 26, 2018
)

When dict subclass overrides order (`__iter__()`, `keys()`, and `items()`), `dict(o)`
should use it instead of dict ordering.

https://bugs.python.org/issue34320
(cherry picked from commit 2aaf98c)

Co-authored-by: INADA Naoki <methane@users.noreply.github.com>
@bedevere-bot
Copy link

GH-9583 is a backport of this pull request to the 3.6 branch.

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Sep 26, 2018
)

When dict subclass overrides order (`__iter__()`, `keys()`, and `items()`), `dict(o)`
should use it instead of dict ordering.

https://bugs.python.org/issue34320
(cherry picked from commit 2aaf98c)

Co-authored-by: INADA Naoki <methane@users.noreply.github.com>
miss-islington added a commit that referenced this pull request Sep 26, 2018
When dict subclass overrides order (`__iter__()`, `keys()`, and `items()`), `dict(o)`
should use it instead of dict ordering.

https://bugs.python.org/issue34320
(cherry picked from commit 2aaf98c)

Co-authored-by: INADA Naoki <methane@users.noreply.github.com>
miss-islington added a commit that referenced this pull request Sep 26, 2018
) (GH-9583)

When dict subclass overrides order (`__iter__()`, `keys()`, and `items()`), `dict(o)`
should use it instead of dict ordering.


https://bugs.python.org/issue34320
(cherry picked from commit 2aaf98c)


Co-authored-by: INADA Naoki <methane@users.noreply.github.com>


https://bugs.python.org/issue34320
@methane methane deleted the dict-copy-order branch November 29, 2018 07:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants