Skip to content

Commit 1a7e7fd

Browse files
gvanrossumicanhasmath
authored andcommitted
bpo-39481: Implementation for PEP 585 (python#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 735dbcc commit 1a7e7fd

30 files changed

+1200
-13
lines changed

Include/Python.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@
122122
#include "iterobject.h"
123123
#include "genobject.h"
124124
#include "descrobject.h"
125+
#include "genericaliasobject.h"
125126
#include "warnings.h"
126127
#include "weakrefobject.h"
127128
#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

@@ -856,6 +880,7 @@ def setdefault(self, key, default=None):
856880
self[key] = default
857881
return default
858882

883+
859884
MutableMapping.register(dict)
860885

861886

@@ -923,6 +948,7 @@ def count(self, value):
923948
'S.count(value) -> integer -- return number of occurrences of value'
924949
return sum(1 for v in self if v is value or v == value)
925950

951+
926952
Sequence.register(tuple)
927953
Sequence.register(str)
928954
Sequence.register(range)
@@ -1007,5 +1033,6 @@ def __iadd__(self, values):
10071033
self.extend(values)
10081034
return self
10091035

1036+
10101037
MutableSequence.register(list)
10111038
MutableSequence.register(bytearray) # Multiply inheriting, see ByteString

Lib/contextlib.py

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

89
__all__ = ["asynccontextmanager", "contextmanager", "closing", "nullcontext",
910
"AbstractContextManager", "AbstractAsyncContextManager",
@@ -15,6 +16,8 @@ class AbstractContextManager(abc.ABC):
1516

1617
"""An abstract base class for context managers."""
1718

19+
__class_getitem__ = classmethod(GenericAlias)
20+
1821
def __enter__(self):
1922
"""Return `self` upon entering the runtime context."""
2023
return self
@@ -35,6 +38,8 @@ class AbstractAsyncContextManager(abc.ABC):
3538

3639
"""An abstract base class for asynchronous context managers."""
3740

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

Lib/os.py

Lines changed: 41 additions & 0 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.
@@ -1081,3 +1083,42 @@ def __subclasshook__(cls, subclass):
10811083
if cls is PathLike:
10821084
return _check_methods(subclass, '__fspath__')
10831085
return NotImplemented
1086+
1087+
__class_getitem__ = classmethod(GenericAlias)
1088+
1089+
1090+
if name == 'nt':
1091+
class _AddedDllDirectory:
1092+
def __init__(self, path, cookie, remove_dll_directory):
1093+
self.path = path
1094+
self._cookie = cookie
1095+
self._remove_dll_directory = remove_dll_directory
1096+
def close(self):
1097+
self._remove_dll_directory(self._cookie)
1098+
self.path = None
1099+
def __enter__(self):
1100+
return self
1101+
def __exit__(self, *args):
1102+
self.close()
1103+
def __repr__(self):
1104+
if self.path:
1105+
return "<AddedDllDirectory({!r})>".format(self.path)
1106+
return "<AddedDllDirectory()>"
1107+
1108+
def add_dll_directory(path):
1109+
"""Add a path to the DLL search path.
1110+
1111+
This search path is used when resolving dependencies for imported
1112+
extension modules (the module itself is resolved through sys.path),
1113+
and also by ctypes.
1114+
1115+
Remove the directory by calling close() on the returned object or
1116+
using it in a with statement.
1117+
"""
1118+
import nt
1119+
cookie = nt._add_dll_directory(path)
1120+
return _AddedDllDirectory(
1121+
path,
1122+
cookie,
1123+
nt._remove_dll_directory
1124+
)

Lib/subprocess.py

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

5657
# Exception classes used by this module.
5758
class SubprocessError(Exception): pass
@@ -437,6 +438,9 @@ def __repr__(self):
437438
args.append('stderr={!r}'.format(self.stderr))
438439
return "{}({})".format(type(self).__name__, ', '.join(args))
439440

441+
__class_getitem__ = classmethod(types.GenericAlias)
442+
443+
440444
def check_returncode(self):
441445
"""Raise CalledProcessError if the exit code is non-zero."""
442446
if self.returncode:
@@ -827,6 +831,20 @@ def __init__(self, args, bufsize=-1, executable=None,
827831

828832
raise
829833

834+
<<<<<<< HEAD
835+
=======
836+
def __repr__(self):
837+
obj_repr = (
838+
f"<{self.__class__.__name__}: "
839+
f"returncode: {self.returncode} args: {list(self.args)!r}>"
840+
)
841+
if len(obj_repr) > 80:
842+
obj_repr = obj_repr[:76] + "...>"
843+
return obj_repr
844+
845+
__class_getitem__ = classmethod(types.GenericAlias)
846+
847+
>>>>>>> 48b069a003 (bpo-39481: Implementation for PEP 585 (#18239))
830848
@property
831849
def universal_newlines(self):
832850
# universal_newlines as retained as an alias of text_mode for API

Lib/tempfile.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
import shutil as _shutil
4444
import errno as _errno
4545
from random import Random as _Random
46+
import sys as _sys
47+
import types as _types
4648
import weakref as _weakref
4749
import _thread
4850
_allocate_lock = _thread.allocate_lock
@@ -644,7 +646,9 @@ def __init__(self, max_size=0, mode='w+b', buffering=-1,
644646
self._TemporaryFileArgs = {'mode': mode, 'buffering': buffering,
645647
'suffix': suffix, 'prefix': prefix,
646648
'encoding': encoding, 'newline': newline,
647-
'dir': dir}
649+
'dir': dir, 'errors': errors}
650+
651+
__class_getitem__ = classmethod(_types.GenericAlias)
648652

649653
def _check(self, file):
650654
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)