Skip to content

Commit e9875ec

Browse files
gh-119180: PEP 649: Add __annotate__ attributes (#119209)
1 parent 73ab83b commit e9875ec

13 files changed

+324
-18
lines changed

Include/cpython/funcobject.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ typedef struct {
4141
PyObject *func_weakreflist; /* List of weak references */
4242
PyObject *func_module; /* The __module__ attribute, can be anything */
4343
PyObject *func_annotations; /* Annotations, a dict or NULL */
44+
PyObject *func_annotate; /* Callable to fill the annotations dictionary */
4445
PyObject *func_typeparams; /* Tuple of active type variables or NULL */
4546
vectorcallfunc vectorcall;
4647
/* Version number for use by specializer.

Include/internal/pycore_global_objects_fini_generated.h

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_global_strings.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ struct _Py_global_strings {
7979
STRUCT_FOR_ID(__all__)
8080
STRUCT_FOR_ID(__and__)
8181
STRUCT_FOR_ID(__anext__)
82+
STRUCT_FOR_ID(__annotate__)
8283
STRUCT_FOR_ID(__annotations__)
8384
STRUCT_FOR_ID(__args__)
8485
STRUCT_FOR_ID(__asyncio_running_event_loop__)

Include/internal/pycore_runtime_init_generated.h

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_unicodeobject_generated.h

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/test/test_sys.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1564,7 +1564,7 @@ def func():
15641564
check(x, size('3Pi2cP7P2ic??2P'))
15651565
# function
15661566
def func(): pass
1567-
check(func, size('15Pi'))
1567+
check(func, size('16Pi'))
15681568
class c():
15691569
@staticmethod
15701570
def foo():

Lib/test/test_type_annotations.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import textwrap
2+
import types
23
import unittest
34
from test.support import run_code
45

@@ -212,3 +213,46 @@ def test_match(self):
212213
case 0:
213214
x: int = 1
214215
""")
216+
217+
218+
class AnnotateTests(unittest.TestCase):
219+
"""See PEP 649."""
220+
def test_manual_annotate(self):
221+
def f():
222+
pass
223+
mod = types.ModuleType("mod")
224+
class X:
225+
pass
226+
227+
for obj in (f, mod, X):
228+
with self.subTest(obj=obj):
229+
self.check_annotations(obj)
230+
231+
def check_annotations(self, f):
232+
self.assertEqual(f.__annotations__, {})
233+
self.assertIs(f.__annotate__, None)
234+
235+
with self.assertRaisesRegex(TypeError, "__annotate__ must be callable or None"):
236+
f.__annotate__ = 42
237+
f.__annotate__ = lambda: 42
238+
with self.assertRaisesRegex(TypeError, r"takes 0 positional arguments but 1 was given"):
239+
print(f.__annotations__)
240+
241+
f.__annotate__ = lambda x: 42
242+
with self.assertRaisesRegex(TypeError, r"__annotate__ returned non-dict of type 'int'"):
243+
print(f.__annotations__)
244+
245+
f.__annotate__ = lambda x: {"x": x}
246+
self.assertEqual(f.__annotations__, {"x": 1})
247+
248+
# Setting annotate to None does not invalidate the cached __annotations__
249+
f.__annotate__ = None
250+
self.assertEqual(f.__annotations__, {"x": 1})
251+
252+
# But setting it to a new callable does
253+
f.__annotate__ = lambda x: {"y": x}
254+
self.assertEqual(f.__annotations__, {"y": 1})
255+
256+
# Setting f.__annotations__ also clears __annotate__
257+
f.__annotations__ = {"z": 43}
258+
self.assertIs(f.__annotate__, None)

Lib/test/test_typing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3723,7 +3723,7 @@ def meth(self): pass
37233723

37243724
acceptable_extra_attrs = {
37253725
'_is_protocol', '_is_runtime_protocol', '__parameters__',
3726-
'__init__', '__annotations__', '__subclasshook__',
3726+
'__init__', '__annotations__', '__subclasshook__', '__annotate__',
37273727
}
37283728
self.assertLessEqual(vars(NonP).keys(), vars(C).keys() | acceptable_extra_attrs)
37293729
self.assertLessEqual(

Lib/typing.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1889,6 +1889,7 @@ class _TypingEllipsis:
18891889
'__init__', '__module__', '__new__', '__slots__',
18901890
'__subclasshook__', '__weakref__', '__class_getitem__',
18911891
'__match_args__', '__static_attributes__', '__firstlineno__',
1892+
'__annotate__',
18921893
})
18931894

18941895
# These special attributes will be not collected as protocol members.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add an ``__annotate__`` attribute to functions, classes, and modules as part
2+
of :pep:`649`. Patch by Jelle Zijlstra.

0 commit comments

Comments
 (0)