GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-145876: Do not mask AttributeErrors raised during dictionary unpacking by serhiy-storchaka · Pull Request #145906 · python/cpython

Conversation

@serhiy-storchaka

@serhiy-storchaka serhiy-storchaka commented Mar 13, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

AttributeErrors raised in keys() or __getitem__() during dictionary unpacking ({**mymapping} or func(**mymapping)) are no longer masked by TypeError.

  • Issue: AttributeErrors raised in .keys() or .__getitem__() during {**mymapping}are incorrectly masked #145876

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@serhiy-storchaka
pythongh-145876: Do not mask AttributeErrors raised during dictionary…
3a3190f
… unpacking

AttributeErrors raised in keys() or __getitem__() during
dictionary unpacking ({**mymapping} or func(**mymapping)) are
no longer masked by TypeError.
@serhiy-storchaka
serhiy-storchaka requested a review from markshannon as a code owner March 13, 2026 11:03
@bedevere-app bedevere-app Bot mentioned this pull request Mar 13, 2026
AttributeErrors raised in .keys() or .__getitem__() during {**mymapping}are incorrectly masked #145876
Closed
@bedevere-app bedevere-app Bot added the awaiting core review label Mar 13, 2026
NickCrews
NickCrews reviewed Mar 18, 2026
View reviewed changes

@NickCrews NickCrews left a comment

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

The tests look good to me, modulo my question about checking for error chaining.

I wanted to point out non-atomic way we are checking for the presence of .keys(). But I'm guessing you had reasons you weren't doing something else.

Thanks for your work here!

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Python/bytecodes.c
"'%.200s' object is not a mapping",
Py_TYPE(update_o)->tp_name);
PyObject *exc = _PyErr_GetRaisedException(tstate);
int has_keys = PyObject_HasAttrWithError(update_o, &_Py_ID(keys));

@NickCrews NickCrews Mar 18, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I suppose a user could be absolutely insane and delete the .keys() method of their object in the middle of throwing their error. Then this check here wouldn't really detect the original cause of the error. Ideally this check was "atomic" with our actual attempt at accessing keys. But this seems like such an edge case that the more performant check-details-only-after-error seems like the right balance of perf vs correctness.

If this API existed, I think it would be better to inspect the exc PyObject to see if it was

  1. from attempting to access update_o
  2. using the "keys" attribute name.
    instead of this PyObject_HasAttrWithError() check

Or, the other more heavy-handed solution would be to adjust the PyDict_Update(dict_o, update_o); line above to be a more explicit API with a result object such as

UpdateResult update_result;
PyDict_Update(dict_o, update_o, &update_result);
if (update_result.error) ...
if (update_result.error_cause = NO_KEYS) ...

but not sure about the perf, maintenance, etc costs of doing this.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@serhiy-storchaka serhiy-storchaka Mar 19, 2026

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Inspecting the exception object is not enough, because the user code can raise an exception which look exactly the same as when accessing the key attribute.. And we will get the original issue. We would need a special variants of PyDict_Merge() and PyMapping_Keys(). This has too much cost.

Note that dict.update() tests the existence of keys prior to calling PyMapping_Keys(), so it looks up keys twice. This adds some overhead, but this is necessary. In dictionary unpacking we can avoid such overhead.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Python/bytecodes.c
Py_DECREF(exc);
}
else {
_PyErr_ChainExceptions1(exc);

@NickCrews NickCrews Mar 18, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I'm assuming that this C API is the thing responsible for the ...during the above exception...another exception was raised... message. Should the test cases above check that the user sees this error chain? Currently it looks like we only check for the root cause of the error.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@serhiy-storchaka serhiy-storchaka Mar 19, 2026

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

This only happens when the second lookup of the keys attribute raises an exception different from AttributeError, while the first lookup was successful or raised an AttributeError. This is very uncommon case and in needs a special complex code for testing. I do not think that it is is worth to do.

The main effect is that if the second lookup of the keys fails, we do not swallow the exception (it can be KeyboardInterrupt). Swallowing exception is always bad.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Python/bytecodes.c
int has_keys = PyObject_HasAttrWithError(update_o, &_Py_ID(keys));
if (has_keys == 0) {
_PyErr_Format(tstate, PyExc_TypeError,
"'%T' object is not a mapping",

@NickCrews NickCrews Mar 18, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Flagging that this is changing the formatting string from '%.200s' to '%T', but I'm assuming that is intentional.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Python/ceval.c
"Value after ** must be a mapping, not %.200s",
Py_TYPE(kwargs)->tp_name);
PyObject *exc = _PyErr_GetRaisedException(tstate);
int has_keys = PyObject_HasAttrWithError(kwargs, &_Py_ID(keys));

@NickCrews NickCrews Mar 18, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Similar to above, I would love a more watertight/atomic way of checking the real cause of the error.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@serhiy-storchaka
Merge branch 'main' into unpack-dict-pass-errors
e02b4d2
@NickCrews

NickCrews commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

Thanks for the responses to my review @serhiy-storchaka. It looks like you already considered all the potential concerns I raised, and you know better than me the pros/cons of the solutions. So this looks good to me!

Is there anything I can do to help move this forward? Would a benchmark be useful to verify this doesn't hurt performance too much? Should I do that by adding a test case to the pyperformance repo, similar to this PR from 2 months ago?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@serhiy-storchaka

serhiy-storchaka commented Mar 26, 2026

Copy link
Copy Markdown
Member Author

Nothing. I only waited if there are any suggestions or objections.

NickCrews reacted with heart emoji
All reactions
  • ❤️ 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@serhiy-storchaka
Merge branch 'main' into unpack-dict-pass-errors
172b210
@serhiy-storchaka
serhiy-storchaka enabled auto-merge (squash) March 26, 2026 13:20
@serhiy-storchaka
serhiy-storchaka merged commit 0e54305 into python:main Mar 26, 2026
76 checks passed

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-app bedevere-app Bot removed the awaiting core review label Mar 26, 2026
@serhiy-storchaka
serhiy-storchaka deleted the unpack-dict-pass-errors branch March 26, 2026 15:12
@NickCrews

NickCrews commented Mar 26, 2026

Copy link
Copy Markdown
Contributor

Thank you!

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@pablogsal pablogsal mentioned this pull request Apr 4, 2026
gh-130273: Fix traceback color output with unicode characters #142529
Merged
clin1234 pushed a commit to clin1234/cpython that referenced this pull request Apr 16, 2026
@serhiy-storchaka @clin1234
pythongh-145876: Do not mask AttributeErrors raised during dictionary…
5329e60
… unpacking (pythonGH-145906)

AttributeErrors raised in keys() or __getitem__() during
dictionary unpacking ({**mymapping} or func(**mymapping)) are
no longer masked by TypeError.
ljfp pushed a commit to ljfp/cpython that referenced this pull request Apr 25, 2026
@serhiy-storchaka @ljfp
pythongh-145876: Do not mask AttributeErrors raised during dictionary…
53f4cfd
… unpacking (pythonGH-145906)

AttributeErrors raised in keys() or __getitem__() during
dictionary unpacking ({**mymapping} or func(**mymapping)) are
no longer masked by TypeError.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@markshannon markshannon Awaiting requested review from markshannon markshannon is a code owner
1 more reviewer
@NickCrews NickCrews NickCrews left review comments
Reviewers whose approvals may not affect merge requirements

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

2 participants

@serhiy-storchaka @NickCrews
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.