Skip to content

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

Description

@NickCrews

Bug description:

I have a custom Mapping type. I am unpacking it with eg {**mymapping}. If, in either the keys() or the the __getitem__() method, I raise most kinds of errors, such as a ValueError, these are reported correctly. BUT, if I raise an AttributeError, then this error isn't reported properly, instead I get TypeError: 'MyMapping' object is not a mapping, masking the actual error:

classMyMapping:
def__init__(
self,
*,
raises_on_keys: type[Exception] |None=None,
raises_on_getitem: type[Exception] |None=None,
):
self.raises_on_keys=raises_on_keysself.raises_on_getitem=raises_on_getitemdef__getitem__(self, key):
ifself.raises_on_getitem:
raiseself.raises_on_getitem("error in __getitem__")
returnkey*2defkeys(self):
ifself.raises_on_keys:
raiseself.raises_on_keys("error in keys")
return [1, 2, 3]
options= [
None,
ValueError,
AttributeError,
]
outcomes= []
forraises_on_keysinoptions:
forraises_on_getiteminoptions:
try:
d= {
**MyMapping(
raises_on_keys=raises_on_keys, raises_on_getitem=raises_on_getitem
)
}
outcomes.append((raises_on_keys, raises_on_getitem, "Success", d))
exceptExceptionase:
outcomes.append((raises_on_keys, raises_on_getitem, "Exception", str(e)))
# format to markdown tableprint("| raises_on_keys | raises_on_getitem | outcome | result |")
print("| --- | --- | --- | --- |")
forraises_on_keys, raises_on_getitem, outcome, resultinoutcomes:
raises_on_keys_str=raises_on_keys.__name__ifraises_on_keyselse"None"raises_on_getitem_str=raises_on_getitem.__name__ifraises_on_getitemelse"None"print(
f"| {raises_on_keys_str} | {raises_on_getitem_str} | {outcome} | `{result}` |"
)

Ran with uv run --python 3.14 bug.py, which resolves to python 3.14.2. This gives:

raises_on_keysraises_on_getitemerror
NoneNone``
NoneValueErrorValueError: error in __getitem__
NoneAttributeErrorTypeError: 'MyMapping' object is not a mapping
ValueErrorNoneValueError: error in keys
ValueErrorValueErrorValueError: error in keys
ValueErrorAttributeErrorValueError: error in keys
AttributeErrorNoneTypeError: 'MyMapping' object is not a mapping
AttributeErrorValueErrorTypeError: 'MyMapping' object is not a mapping
AttributeErrorAttributeErrorTypeError: 'MyMapping' object is not a mapping

What I would expect is for all of the TypeError: 'MyMapping' object is not a mapping errors to actually be AttributeError: error in keys or AttributeError: error in __getitem__ errors.

I assume this is because in the implementation, it does assumes ducktyping, and the raised attribute error is interpreted as "the passed object doesn't even have a keys()/__getitem__ method"

eg guessing this is how this is currently implemented:

try:
forkeyinobj.keys():
yieldkey, obj.__getitem__(key)
exceptAttributeErrorase:
raiseTypeError(f"'{type(obj).__name__}' object is not a mapping")

What I think SHOULD happen:

try:
keys=obj.keysexceptAttributeErrorase:
raiseTypeError(f"'{type(obj).__name__}' object is not a mapping")
forkeyinkeys():
try:
getter=obj.__getitem__exceptAttributeErrorase:
raiseTypeError(f"'{type(obj).__name__}' object is not a mapping")
yieldkey, getter(key)

EDIT: Actually this should be more performant, only 2 checks, instead of N checks, one per key. (Also, for the record, this includes suggestion to improve the error messages, but that should definitely be a separate PR)

try:
keys=obj.keysexceptAttributeErrorase:
raiseTypeError(f"'{type(obj).__name__}' object requires a .keys() method to be used as a mapping")
try:
getter=obj.__getitem__exceptAttributeErrorase:
raiseTypeError(f"'{type(obj).__name__}' object requires a .__getitem__() method to be used as a mapping")
forkeyinkeys():
yieldkey, getter(key)

CPython versions tested on:

3.14

Operating systems tested on:

macOS

Linked PRs

Metadata

Metadata

Labels

3.15pre-release feature fixes, bugs and security fixesinterpreter-core(Objects, Python, Grammar, and Parser dirs)type-bugAn unexpected behavior, bug, or errortype-featureA feature request or enhancement

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions