8000 bpo-46571: improve `typing.no_type_check` to skip foreign objects · python/cpython@a03009c · GitHub
[go: up one dir, main page]

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit a03009c

Browse files
committed
bpo-46571: improve typing.no_type_check to skip foreign objects
1 parent ee0ac32 commit a03009c

File tree

4 files changed

+75
-7
lines changed

4 files changed

+75
-7
lines changed

Doc/library/typing.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1999,8 +1999,8 @@ Functions and decorators
19991999
Decorator to indicate that annotations are not type hints.
20002000

20012001
This works as class or function :term:`decorator`. With a class, it
2002-
applies recursively to all methods defined in that class (but not
2003-
to methods defined in its superclasses or subclasses).
2002+
applies recursively to all methods and classes defined in that class
2003+
(but not to methods defined in its superclasses or subclasses).
20042004

20052005
This mutates the function(s) in place.
20062006

Lib/test/test_typing.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2965,9 +2965,73 @@ def meth(self, x: int): ...
29652965
@no_type_check
29662966
class D(C):
29672967
c = C
2968+
29682969
# verify that @no_type_check never affects bases
29692970
self.assertEqual(get_type_hints(C.meth), {'x': int})
29702971

2972+
# and never child classes:
2973+
class Child(D):
2974+
def foo(self, x: int): ...
2975+
2976+
self.assertEqual(get_type_hints(Child.foo), {'x': int})
2977+
2978+
def test_no_type_check_nested_types(self):
2979+
# See https://bugs.python.org/issue46571
2980+
class Other:
2981+
o: int
2982+
class B: # Has the same `__name__`` as `A.B` and different `__qualname__`
2983+
o: int
2984+
@no_type_check
2985+
class A:
2986+
a: int
2987+
class B:
2988+
b: int
2989+
class C:
2990+
c: int
2991+
class D:
2992+
d: int
2993+
2994+
Other = Other
2995+
2996+
for klass in [A, A.B, A.B.C, A.D]:
2997+
with self.subTest(klass=klass):
2998+
self.assertTrue(klass.__no_type_check__)
2999+
self.assertEqual(get_type_hints(klass), {})
3000+
3001+
for not_modified in [Other, B]:
3002+
with self.subTest(not_modified=not_modified):
3003+
with self.assertRaises(AttributeError):
3004+
not_modified.__no_type_check__
3005+
self.assertNotEqual(get_type_hints(not_modified), {})
3006+
3007+
def test_no_type_check_foreign_functions(self):
3008+
# We should not modify this function:
3009+
def some(*args: int) -> int:
3010+
...
3011+
3012+
@no_type_check
3013+
class A:
3014+
some_alias = some
3015+
some_class = classmethod(some)
3016+
some_static = staticmethod(some)
3017+
3018+
with self.assertRaises(AttributeError):
3019+
some.__no_type_check__
3020+
3021+
def test_no_type_check_lambda(self):
3022+
@no_type_check
3023+
class A:
3024+
# Corner case: `lambda` is both an assignment and a function:
3025+
bar: Callable[[int], int] = lambda arg: arg
3026+
3027+
self.assertTrue(A.bar.__no_type_check__)
3028+
self.assertEqual(get_type_hints(A.bar), {})
3029+
3030+
def test_no_type_check_TypeError(self):
3031+
# This simply should not fail with
3032+
# `TypeError: can't set attributes of built-in/extension type 'dict'`
3033+
no_type_check(dict)
3034+
29713035
def test_no_type_check_forward_ref_as_string(self):
29723036
class C:
29733037
foo: typing.ClassVar[int] = 7

Lib/typing.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1959,11 +1959,13 @@ def no_type_check(arg):
19591959
This mutates the function(s) or class(es) in place.
19601960
"""
19611961
if isinstance(arg, type):
1962-
arg_attrs = arg.__dict__.copy()
1963-
for attr, val in arg.__dict__.items():
1964-
if val in arg.__bases__ + (arg,):
1965-
arg_attrs.pop(attr)
1966-
for obj in arg_attrs.values():
1962+
for obj in arg.__dict__.values():
1963+
if (hasattr(obj, '__qualname__')
1964+
and obj.__qualname__ != f'{arg.__qualname__}.{obj.__name__}'):
1965+
# We only modify objects that are defined in this type directly.
1966+
# If classes / methods are nested in multiple layers,
1967+
# we will modify them when processing their direct holders.
1968+
continue
19671969
if isinstance(obj, types.FunctionType):
19681970
obj.__no_type_check__ = True
19691971
if isinstance(obj, type):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Improve :func:`typing.no_type_check` to not modify foreign functions and
2+
types.

0 commit comments

Comments
 (0)
0