Skip to content

Commit 48b069a

Browse files
authored
bpo-39481: Implementation for PEP 585 (#18239)
This implements things like `list[int]`, which returns an object of type `types.GenericAlias`. This object mostly acts as a proxy for `list`, but has attributes `__origin__` and `__args__` that allow recovering the parts (with values `list` and `(int,)`. There is also an approximate notion of type variables; e.g. `list[T]` has a `__parameters__` attribute equal to `(T,)`. Type variables are objects of type `typing.TypeVar`.
1 parent 9cc3ebd commit 48b069a

30 files changed

+830
-53
lines changed

Include/Python.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
#include "iterobject.h"
121121
#include "genobject.h"
122122
#include "descrobject.h"
123+
#include "genericaliasobject.h"
123124
#include "warnings.h"
124125
#include "weakrefobject.h"
125126
#include "structseq.h"

Include/genericaliasobject.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Implementation of PEP 585: support list[int] etc.
2+
#ifndef Py_GENERICALIASOBJECT_H
3+
#define Py_GENERICALIASOBJECT_H
4+
#ifdef __cplusplus
5+
extern "C" {
6+
#endif
7+
8+
PyAPI_FUNC(PyObject *) Py_GenericAlias(PyObject *, PyObject *);
9+
PyAPI_DATA(PyTypeObject) Py_GenericAliasType;
10+
11+
#ifdef __cplusplus
12+
}
13+
#endif
14+
#endif /* !Py_GENERICALIASOBJECT_H */

Lib/_collections_abc.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
from abc import ABCMeta, abstractmethod
1010
import sys
1111

12+
GenericAlias = type(list[int])
13+
1214
__all__ = ["Awaitable", "Coroutine",
1315
"AsyncIterable", "AsyncIterator", "AsyncGenerator",
1416
"Hashable", "Iterable", "Iterator", "Generator", "Reversible",
@@ -110,6 +112,8 @@ def __subclasshook__(cls, C):
110112
return _check_methods(C, "__await__")
111113
return NotImplemented
112114

115+
__class_getitem__ = classmethod(GenericAlias)
116+
113117

114118
class Coroutine(Awaitable):
115119

@@ -169,6 +173,8 @@ def __subclasshook__(cls, C):
169173
return _check_methods(C, "__aiter__")
170174
return NotImplemented
171175

176+
__class_getitem__ = classmethod(GenericAlias)
177+
172178

173179
class AsyncIterator(AsyncIterable):
174180

@@ -255,6 +261,8 @@ def __subclasshook__(cls, C):
255261
return _check_methods(C, "__iter__")
256262
return NotImplemented
257263

264+
__class_getitem__ = classmethod(GenericAlias)
265+
258266

259267
class Iterator(Iterable):
260268

@@ -274,6 +282,7 @@ def __subclasshook__(cls, C):
274282
return _check_methods(C, '__iter__', '__next__')
275283
return NotImplemented
276284

285+
277286
Iterator.register(bytes_iterator)
278287
Iterator.register(bytearray_iterator)
279288
#Iterator.register(callable_iterator)
@@ -353,6 +362,7 @@ def __subclasshook__(cls, C):
353362
'send', 'throw', 'close')
354363
return NotImplemented
355364

365+
356366
Generator.register(generator)
357367

358368

@@ -385,6 +395,9 @@ def __subclasshook__(cls, C):
385395
return _check_methods(C, "__contains__")
386396
return NotImplemented
387397

398+
__class_getitem__ = classmethod(GenericAlias)
399+
400+
388401
class Collection(Sized, Iterable, Container):
389402

390403
__slots__ = ()
@@ -395,6 +408,7 @@ def __subclasshook__(cls, C):
395408
return _check_methods(C, "__len__", "__iter__", "__contains__")
396409
return NotImplemented
397410

411+
398412
class Callable(metaclass=ABCMeta):
399413

400414
__slots__ = ()
@@ -409,6 +423,8 @@ def __subclasshook__(cls, C):
409423
return _check_methods(C, "__call__")
410424
return NotImplemented
411425

426+
__class_getitem__ = classmethod(GenericAlias)
427+
412428

413429
### SETS ###
414430

@@ -550,6 +566,7 @@ def _hash(self):
550566
h = 590923713
551567
return h
552568

569+
553570
Set.register(frozenset)
554571

555572

@@ -632,6 +649,7 @@ def __isub__(self, it):
632649
self.discard(value)
633650
return self
634651

652+
635653
MutableSet.register(set)
636654

637655

@@ -688,6 +706,7 @@ def __eq__(self, other):
688706

689707
__reversed__ = None
690708

709+
691710
Mapping.register(mappingproxy)
692711

693712

@@ -704,6 +723,8 @@ def __len__(self):
704723
def __repr__(self):
705724
return '{0.__class__.__name__}({0._mapping!r})'.format(self)
706725

726+
__class_getitem__ = classmethod(GenericAlias)
727+
707728

708729
class KeysView(MappingView, Set):
709730

@@ -719,6 +740,7 @@ def __contains__(self, key):
719740
def __iter__(self):
720741
yield from self._mapping
721742

743+
722744
KeysView.register(dict_keys)
723745

724746

@@ -743,6 +765,7 @@ def __iter__(self):
743765
for key in self._mapping:
744766
yield (key, self._mapping[key])
745767

768+
746769
ItemsView.register(dict_items)
747770

748771

@@ -761,6 +784,7 @@ def __iter__(self):
761784
for key in self._mapping:
762785
yield self._mapping[key]
763786

787+
764788
ValuesView.register(dict_values)
765789

766790

@@ -847,6 +871,7 @@ def setdefault(self, key, default=None):
847871
self[key] = default
848872
return default
849873

874+
850875
MutableMapping.register(dict)
851876

852877

@@ -914,6 +939,7 @@ def count(self, value):
914939
'S.count(value) -> integer -- return number of occurrences of value'
915940
return sum(1 for v in self if v is value or v == value)
916941

942+
917943
Sequence.register(tuple)
918944
Sequence.register(str)
919945
Sequence.register(range)
@@ -1000,5 +1026,6 @@ def __iadd__(self, values):
10001026
self.extend(values)
10011027
return self
10021028

1029+
10031030
MutableSequence.register(list)
10041031
MutableSequence.register(bytearray) # Multiply inheriting, see ByteString

Lib/contextlib.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import _collections_abc
55
from collections import deque
66
from functools import wraps
7-
from types import MethodType
7+
from types import MethodType, GenericAlias
88

99
__all__ = ["asynccontextmanager", "contextmanager", "closing", "nullcontext",
1010
"AbstractContextManager", "AbstractAsyncContextManager",
@@ -16,6 +16,8 @@ class AbstractContextManager(abc.ABC):
1616

1717
"""An abstract base class for context managers."""
1818

19+
__class_getitem__ = classmethod(GenericAlias)
20+
1921
def __enter__(self):
2022
"""Return `self` upon entering the runtime context."""
2123
return self
@@ -36,6 +38,8 @@ class AbstractAsyncContextManager(abc.ABC):
3638

3739
"""An abstract base class for asynchronous context managers."""
3840

41+
__class_getitem__ = classmethod(GenericAlias)
42+
3943
async def __aenter__(self):
4044
"""Return `self` upon entering the runtime context."""
4145
return self

Lib/os.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828

2929
from _collections_abc import _check_methods
3030

31+
GenericAlias = type(list[int])
32+
3133
_names = sys.builtin_module_names
3234

3335
# Note: more names are added to __all__ later.
@@ -1074,8 +1076,7 @@ def __subclasshook__(cls, subclass):
10741076
return _check_methods(subclass, '__fspath__')
10751077
return NotImplemented
10761078

1077-
def __class_getitem__(cls, type):
1078-
return cls
1079+
__class_getitem__ = classmethod(GenericAlias)
10791080

10801081

10811082
if name == 'nt':

Lib/subprocess.py

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import warnings
5353
import contextlib
5454
from time import monotonic as _time
55+
import types
5556

5657
try:
5758
import pwd
@@ -446,17 +447,7 @@ def __repr__(self):
446447
args.append('stderr={!r}'.format(self.stderr))
447448
return "{}({})".format(type(self).__name__, ', '.join(args))
448449

449-
def __class_getitem__(cls, type):
450-
"""Provide minimal support for using this class as generic
451-
(for example in type annotations).
452-
453-
See PEP 484 and PEP 560 for more details. For example,
454-
`CompletedProcess[bytes]` is a valid expression at runtime
455-
(type argument `bytes` indicates the type used for stdout).
456-
Note, no type checking happens at runtime, but a static type
457-
checker can be used.
458-
"""
459-
return cls
450+
__class_getitem__ = classmethod(types.GenericAlias)
460451

461452

462453
def check_returncode(self):
@@ -1000,16 +991,7 @@ def __repr__(self):
1000991
obj_repr = obj_repr[:76] + "...>"
1001992
return obj_repr
1002993

1003-
def __class_getitem__(cls, type):
1004-
"""Provide minimal support for using this class as generic
1005-
(for example in type annotations).
1006-
1007-
See PEP 484 and PEP 560 for more details. For example, `Popen[bytes]`
1008-
is a valid expression at runtime (type argument `bytes` indicates the
1009-
type used for stdout). Note, no type checking happens at runtime, but
1010-
a static type checker can be used.
1011-
"""
1012-
return cls
994+
__class_getitem__ = classmethod(types.GenericAlias)
1013995

1014996
@property
1015997
def universal_newlines(self):

Lib/tempfile.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import errno as _errno
4545
from random import Random as _Random
4646
import sys as _sys
47+
import types as _types
4748
import weakref as _weakref
4849
import _thread
4950
_allocate_lock = _thread.allocate_lock
@@ -643,17 +644,7 @@ def __init__(self, max_size=0, mode='w+b', buffering=-1,
643644
'encoding': encoding, 'newline': newline,
644645
'dir': dir, 'errors': errors}
645646

646-
def __class_getitem__(cls, type):
647-
"""Provide minimal support for using this class as generic
648-
(for example in type annotations).
649-
650-
See PEP 484 and PEP 560 for more details. For example,
651-
`SpooledTemporaryFile[str]` is a valid expression at runtime (type
652-
argument `str` indicates whether the file is open in bytes or text
653-
mode). Note, no type checking happens at runtime, but a static type
654-
checker can be used.
655-
"""
656-
return cls
647+
__class_getitem__ = classmethod(_types.GenericAlias)
657648

658649
def _check(self, file):
659650
if self._rolled: return

Lib/test/test_descrtut.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ def merge(self, other):
167167
>>> pprint.pprint(dir(list)) # like list.__dict__.keys(), but sorted
168168
['__add__',
169169
'__class__',
170+
'__class_getitem__',
170171
'__contains__',
171172
'__delattr__',
172173
'__delitem__',

Lib/test/test_doctest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -665,7 +665,7 @@ def non_Python_modules(): r"""
665665
666666
>>> import builtins
667667
>>> tests = doctest.DocTestFinder().find(builtins)
668-
>>> 800 < len(tests) < 820 # approximate number of objects with docstrings
668+
>>> 810 < len(tests) < 830 # approximate number of objects with docstrings
669669
True
670670
>>> real_tests = [t for t in tests if len(t.examples) > 0]
671671
>>> len(real_tests) # objects that actually have doctests

0 commit comments

Comments
 (0)