Skip to content

Commit 0e54305

Browse files
gh-145876: Do not mask AttributeErrors raised during dictionary unpacking (GH-145906)
AttributeErrors raised in keys() or __getitem__() during dictionary unpacking ({**mymapping} or func(**mymapping)) are no longer masked by TypeError.
1 parent e1d4823 commit 0e54305

File tree

8 files changed

+418
-16
lines changed

8 files changed

+418
-16
lines changed

Lib/test/test_extcall.py

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,22 @@
329329
...
330330
TypeError: Value after ** must be a mapping, not function
331331
332+
>>> class OnlyKeys:
333+
... def keys(self):
334+
... return ['key']
335+
>>> h(**OnlyKeys())
336+
Traceback (most recent call last):
337+
...
338+
TypeError: 'OnlyKeys' object is not subscriptable
339+
340+
>>> class BrokenKeys:
341+
... def keys(self):
342+
... return 1
343+
>>> h(**BrokenKeys())
344+
Traceback (most recent call last):
345+
...
346+
TypeError: test.test_extcall.BrokenKeys.keys() must return an iterable, not int
347+
332348
>>> dir(b=1, **{'b': 1})
333349
Traceback (most recent call last):
334350
...
@@ -540,6 +556,151 @@
540556
541557
"""
542558

559+
def test_errors_in_iter():
560+
"""
561+
>>> class A:
562+
... def __iter__(self):
563+
... raise exc
564+
...
565+
>>> def f(*args, **kwargs): pass
566+
>>> exc = ZeroDivisionError('some error')
567+
>>> f(*A())
568+
Traceback (most recent call last):
569+
...
570+
ZeroDivisionError: some error
571+
572+
>>> exc = AttributeError('some error')
573+
>>> f(*A())
574+
Traceback (most recent call last):
575+
...
576+
AttributeError: some error
577+
578+
>>> exc = TypeError('some error')
579+
>>> f(*A())
580+
Traceback (most recent call last):
581+
...
582+
TypeError: some error
583+
"""
584+
585+
def test_errors_in_next():
586+
"""
587+
>>> class I:
588+
... def __iter__(self):
589+
... return self
590+
... def __next__(self):
591+
... raise exc
592+
...
593+
>>> class A:
594+
... def __iter__(self):
595+
... return I()
596+
...
597+
598+
>>> def f(*args, **kwargs): pass
599+
>>> exc = ZeroDivisionError('some error')
600+
>>> f(*A())
601+
Traceback (most recent call last):
602+
...
603+
ZeroDivisionError: some error
604+
605+
>>> exc = AttributeError('some error')
606+
>>> f(*A())
607+
Traceback (most recent call last):
608+
...
609+
AttributeError: some error
610+
611+
>>> exc = TypeError('some error')
612+
>>> f(*A())
613+
Traceback (most recent call last):
614+
...
615+
TypeError: some error
616+
"""
617+
618+
def test_errors_in_keys():
619+
"""
620+
>>> class D:
621+
... def keys(self):
622+
... raise exc
623+
...
624+
>>> def f(*args, **kwargs): pass
625+
>>> exc = ZeroDivisionError('some error')
626+
>>> f(**D())
627+
Traceback (most recent call last):
628+
...
629+
ZeroDivisionError: some error
630+
631+
>>> exc = AttributeError('some error')
632+
>>> f(**D())
633+
Traceback (most recent call last):
634+
...
635+
AttributeError: some error
636+
637+
>>> exc = TypeError('some error')
638+
>>> f(**D())
639+
Traceback (most recent call last):
640+
...
641+
TypeError: some error
642+
"""
643+
644+
def test_errors_in_keys_next():
645+
"""
646+
>>> class I:
647+
... def __iter__(self):
648+
... return self
649+
... def __next__(self):
650+
... raise exc
651+
...
652+
>>> class D:
653+
... def keys(self):
654+
... return I()
655+
...
656+
>>> def f(*args, **kwargs): pass
657+
>>> exc = ZeroDivisionError('some error')
658+
>>> f(**D())
659+
Traceback (most recent call last):
660+
...
661+
ZeroDivisionError: some error
662+
663+
>>> exc = AttributeError('some error')
664+
>>> f(**D())
665+
Traceback (most recent call last):
666+
...
667+
AttributeError: some error
668+
669+
>>> exc = TypeError('some error')
670+
>>> f(**D())
671+
Traceback (most recent call last):
672+
...
673+
TypeError: some error
674+
"""
675+
676+
def test_errors_in_getitem():
677+
"""
678+
>>> class D:
679+
... def keys(self):
680+
... return ['key']
681+
... def __getitem__(self, key):
682+
... raise exc
683+
...
684+
>>> def f(*args, **kwargs): pass
685+
>>> exc = ZeroDivisionError('some error')
686+
>>> f(**D())
687+
Traceback (most recent call last):
688+
...
689+
ZeroDivisionError: some error
690+
691+
>>> exc = AttributeError('some error')
692+
>>> f(**D())
693+
Traceback (most recent call last):
694+
...
695+
AttributeError: some error
696+
697+
>>> exc = TypeError('some error')
698+
>>> f(**D())
699+
Traceback (most recent call last):
700+
...
701+
TypeError: some error
702+
"""
703+
543704
import doctest
544705
import unittest
545706

Lib/test/test_unpack_ex.py

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,22 @@
134134
...
135135
TypeError: 'list' object is not a mapping
136136
137+
>>> class OnlyKeys:
138+
... def keys(self):
139+
... return ['key']
140+
>>> {**OnlyKeys()}
141+
Traceback (most recent call last):
142+
...
143+
TypeError: 'OnlyKeys' object is not subscriptable
144+
145+
>>> class BrokenKeys:
146+
... def keys(self):
147+
... return 1
148+
>>> {**BrokenKeys()}
149+
Traceback (most recent call last):
150+
...
151+
TypeError: test.test_unpack_ex.BrokenKeys.keys() must return an iterable, not int
152+
137153
>>> len(eval("{" + ", ".join("**{{{}: {}}}".format(i, i)
138154
... for i in range(1000)) + "}"))
139155
1000
@@ -560,6 +576,176 @@
560576
561577
"""
562578

579+
def test_errors_in_iter():
580+
"""
581+
>>> class A:
582+
... def __iter__(self):
583+
... raise exc
584+
...
585+
>>> exc = ZeroDivisionError('some error')
586+
>>> [*A()]
587+
Traceback (most recent call last):
588+
...
589+
ZeroDivisionError: some error
590+
591+
>>> {*A()}
592+
Traceback (most recent call last):
593+
...
594+
ZeroDivisionError: some error
595+
596+
>>> exc = AttributeError('some error')
597+
>>> [*A()]
598+
Traceback (most recent call last):
599+
...
600+
AttributeError: some error
601+
602+
>>> {*A()}
603+
Traceback (most recent call last):
604+
...
605+
AttributeError: some error
606+
607+
>>> exc = TypeError('some error')
608+
>>> [*A()]
609+
Traceback (most recent call last):
610+
...
611+
TypeError: some error
612+
613+
>>> {*A()}
614+
Traceback (most recent call last):
615+
...
616+
TypeError: some error
617+
"""
618+
619+
def test_errors_in_next():
620+
"""
621+
>>> class I:
622+
... def __iter__(self):
623+
... return self
624+
... def __next__(self):
625+
... raise exc
626+
...
627+
>>> class A:
628+
... def __iter__(self):
629+
... return I()
630+
...
631+
632+
>>> exc = ZeroDivisionError('some error')
633+
>>> [*A()]
634+
Traceback (most recent call last):
635+
...
636+
ZeroDivisionError: some error
637+
638+
>>> {*A()}
639+
Traceback (most recent call last):
640+
...
641+
ZeroDivisionError: some error
642+
643+
>>> exc = AttributeError('some error')
644+
>>> [*A()]
645+
Traceback (most recent call last):
646+
...
647+
AttributeError: some error
648+
649+
>>> {*A()}
650+
Traceback (most recent call last):
651+
...
652+
AttributeError: some error
653+
654+
>>> exc = TypeError('some error')
655+
>>> [*A()]
656+
Traceback (most recent call last):
657+
...
658+
TypeError: some error
659+
660+
>>> {*A()}
661+
Traceback (most recent call last):
662+
...
663+
TypeError: some error
664+
"""
665+
666+
def test_errors_in_keys():
667+
"""
668+
>>> class D:
669+
... def keys(self):
670+
... raise exc
671+
...
672+
>>> exc = ZeroDivisionError('some error')
673+
>>> {**D()}
674+
Traceback (most recent call last):
675+
...
676+
ZeroDivisionError: some error
677+
678+
>>> exc = AttributeError('some error')
679+
>>> {**D()}
680+
Traceback (most recent call last):
681+
...
682+
AttributeError: some error
683+
684+
>>> exc = TypeError('some error')
685+
>>> {**D()}
686+
Traceback (most recent call last):
687+
...
688+
TypeError: some error
689+
"""
690+
691+
def test_errors_in_keys_next():
692+
"""
693+
>>> class I:
694+
... def __iter__(self):
695+
... return self
696+
... def __next__(self):
697+
... raise exc
698+
...
699+
>>> class D:
700+
... def keys(self):
701+
... return I()
702+
...
703+
>>> exc = ZeroDivisionError('some error')
704+
>>> {**D()}
705+
Traceback (most recent call last):
706+
...
707+
ZeroDivisionError: some error
708+
709+
>>> exc = AttributeError('some error')
710+
>>> {**D()}
711+
Traceback (most recent call last):
712+
...
713+
AttributeError: some error
714+
715+
>>> exc = TypeError('some error')
716+
>>> {**D()}
717+
Traceback (most recent call last):
718+
...
719+
TypeError: some error
720+
"""
721+
722+
def test_errors_in_getitem():
723+
"""
724+
>>> class D:
725+
... def keys(self):
726+
... return ['key']
727+
... def __getitem__(self, key):
728+
... raise exc
729+
...
730+
>>> exc = ZeroDivisionError('some error')
731+
>>> {**D()}
732+
Traceback (most recent call last):
733+
...
734+
ZeroDivisionError: some error
735+
736+
>>> exc = AttributeError('some error')
737+
>>> {**D()}
738+
Traceback (most recent call last):
739+
...
740+
AttributeError: some error
741+
742+
>>> exc = TypeError('some error')
743+
>>> {**D()}
744+
Traceback (most recent call last):
745+
...
746+
TypeError: some error
747+
"""
748+
563749
__test__ = {'doctests' : doctests}
564750

565751
def load_tests(loader, tests, pattern):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:exc:`AttributeError`\ s raised in :meth:`!keys` or :meth:`!__getitem__`
2+
during dictionary unpacking (``{**mymapping}`` or ``func(**mymapping)``) are
3+
no longer masked by :exc:`TypeError`.

0 commit comments

Comments
 (0)