Skip to content

Commit

Permalink
bpo-39481: Implementation for PEP 585 (python#18239)
Browse files Browse the repository at this point in the history
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`.
  • Loading branch information
gvanrossum authored and icanhasmath committed Sep 29, 2023
1 parent 735dbcc commit 1a7e7fd
Show file tree
Hide file tree
Showing 30 changed files with 1,200 additions and 13 deletions.
1 change: 1 addition & 0 deletions Include/Python.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@
#include "iterobject.h"
#include "genobject.h"
#include "descrobject.h"
#include "genericaliasobject.h"
#include "warnings.h"
#include "weakrefobject.h"
#include "structseq.h"
Expand Down
14 changes: 14 additions & 0 deletions Include/genericaliasobject.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Implementation of PEP 585: support list[int] etc.
#ifndef Py_GENERICALIASOBJECT_H
#define Py_GENERICALIASOBJECT_H
#ifdef __cplusplus
extern "C" {
#endif

PyAPI_FUNC(PyObject *) Py_GenericAlias(PyObject *, PyObject *);
PyAPI_DATA(PyTypeObject) Py_GenericAliasType;

#ifdef __cplusplus
}
#endif
#endif /* !Py_GENERICALIASOBJECT_H */
27 changes: 27 additions & 0 deletions Lib/_collections_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from abc import ABCMeta, abstractmethod
import sys

GenericAlias = type(list[int])

__all__ = ["Awaitable", "Coroutine",
"AsyncIterable", "AsyncIterator", "AsyncGenerator",
"Hashable", "Iterable", "Iterator", "Generator", "Reversible",
Expand Down Expand Up @@ -110,6 +112,8 @@ def __subclasshook__(cls, C):
return _check_methods(C, "__await__")
return NotImplemented

__class_getitem__ = classmethod(GenericAlias)


class Coroutine(Awaitable):

Expand Down Expand Up @@ -169,6 +173,8 @@ def __subclasshook__(cls, C):
return _check_methods(C, "__aiter__")
return NotImplemented

__class_getitem__ = classmethod(GenericAlias)


class AsyncIterator(AsyncIterable):

Expand Down Expand Up @@ -255,6 +261,8 @@ def __subclasshook__(cls, C):
return _check_methods(C, "__iter__")
return NotImplemented

__class_getitem__ = classmethod(GenericAlias)


class Iterator(Iterable):

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


Iterator.register(bytes_iterator)
Iterator.register(bytearray_iterator)
#Iterator.register(callable_iterator)
Expand Down Expand Up @@ -353,6 +362,7 @@ def __subclasshook__(cls, C):
'send', 'throw', 'close')
return NotImplemented


Generator.register(generator)


Expand Down Expand Up @@ -385,6 +395,9 @@ def __subclasshook__(cls, C):
return _check_methods(C, "__contains__")
return NotImplemented

__class_getitem__ = classmethod(GenericAlias)


class Collection(Sized, Iterable, Container):

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


class Callable(metaclass=ABCMeta):

__slots__ = ()
Expand All @@ -409,6 +423,8 @@ def __subclasshook__(cls, C):
return _check_methods(C, "__call__")
return NotImplemented

__class_getitem__ = classmethod(GenericAlias)


### SETS ###

Expand Down Expand Up @@ -550,6 +566,7 @@ def _hash(self):
h = 590923713
return h


Set.register(frozenset)


Expand Down Expand Up @@ -632,6 +649,7 @@ def __isub__(self, it):
self.discard(value)
return self


MutableSet.register(set)


Expand Down Expand Up @@ -688,6 +706,7 @@ def __eq__(self, other):

__reversed__ = None


Mapping.register(mappingproxy)


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

__class_getitem__ = classmethod(GenericAlias)


class KeysView(MappingView, Set):

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


KeysView.register(dict_keys)


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


ItemsView.register(dict_items)


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


ValuesView.register(dict_values)


Expand Down Expand Up @@ -856,6 +880,7 @@ def setdefault(self, key, default=None):
self[key] = default
return default


MutableMapping.register(dict)


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


Sequence.register(tuple)
Sequence.register(str)
Sequence.register(range)
Expand Down Expand Up @@ -1007,5 +1033,6 @@ def __iadd__(self, values):
self.extend(values)
return self


MutableSequence.register(list)
MutableSequence.register(bytearray) # Multiply inheriting, see ByteString
5 changes: 5 additions & 0 deletions Lib/contextlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import _collections_abc
from collections import deque
from functools import wraps
from types import MethodType, GenericAlias

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

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

__class_getitem__ = classmethod(GenericAlias)

def __enter__(self):
"""Return `self` upon entering the runtime context."""
return self
Expand All @@ -35,6 +38,8 @@ class AbstractAsyncContextManager(abc.ABC):

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

__class_getitem__ = classmethod(GenericAlias)

async def __aenter__(self):
"""Return `self` upon entering the runtime context."""
return self
Expand Down
41 changes: 41 additions & 0 deletions Lib/os.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@

from _collections_abc import _check_methods

GenericAlias = type(list[int])

_names = sys.builtin_module_names

# Note: more names are added to __all__ later.
Expand Down Expand Up @@ -1081,3 +1083,42 @@ def __subclasshook__(cls, subclass):
if cls is PathLike:
return _check_methods(subclass, '__fspath__')
return NotImplemented

__class_getitem__ = classmethod(GenericAlias)


if name == 'nt':
class _AddedDllDirectory:
def __init__(self, path, cookie, remove_dll_directory):
self.path = path
self._cookie = cookie
self._remove_dll_directory = remove_dll_directory
def close(self):
self._remove_dll_directory(self._cookie)
self.path = None
def __enter__(self):
return self
def __exit__(self, *args):
self.close()
def __repr__(self):
if self.path:
return "<AddedDllDirectory({!r})>".format(self.path)
return "<AddedDllDirectory()>"

def add_dll_directory(path):
"""Add a path to the DLL search path.
This search path is used when resolving dependencies for imported
extension modules (the module itself is resolved through sys.path),
and also by ctypes.
Remove the directory by calling close() on the returned object or
using it in a with statement.
"""
import nt
cookie = nt._add_dll_directory(path)
return _AddedDllDirectory(
path,
cookie,
nt._remove_dll_directory
)
18 changes: 18 additions & 0 deletions Lib/subprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import warnings
import errno
from time import monotonic as _time
import types

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

__class_getitem__ = classmethod(types.GenericAlias)


def check_returncode(self):
"""Raise CalledProcessError if the exit code is non-zero."""
if self.returncode:
Expand Down Expand Up @@ -827,6 +831,20 @@ def __init__(self, args, bufsize=-1, executable=None,

raise

<<<<<<< HEAD
=======
def __repr__(self):
obj_repr = (
f"<{self.__class__.__name__}: "
f"returncode: {self.returncode} args: {list(self.args)!r}>"
)
if len(obj_repr) > 80:
obj_repr = obj_repr[:76] + "...>"
return obj_repr

__class_getitem__ = classmethod(types.GenericAlias)

>>>>>>> 48b069a003 (bpo-39481: Implementation for PEP 585 (#18239))
@property
def universal_newlines(self):
# universal_newlines as retained as an alias of text_mode for API
Expand Down
6 changes: 5 additions & 1 deletion Lib/tempfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
import shutil as _shutil
import errno as _errno
from random import Random as _Random
import sys as _sys
import types as _types
import weakref as _weakref
import _thread
_allocate_lock = _thread.allocate_lock
Expand Down Expand Up @@ -644,7 +646,9 @@ def __init__(self, max_size=0, mode='w+b', buffering=-1,
self._TemporaryFileArgs = {'mode': mode, 'buffering': buffering,
'suffix': suffix, 'prefix': prefix,
'encoding': encoding, 'newline': newline,
'dir': dir}
'dir': dir, 'errors': errors}

__class_getitem__ = classmethod(_types.GenericAlias)

def _check(self, file):
if self._rolled: return
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_descrtut.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ def merge(self, other):
>>> pprint.pprint(dir(list)) # like list.__dict__.keys(), but sorted
['__add__',
'__class__',
'__class_getitem__',
'__contains__',
'__delattr__',
'__delitem__',
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_doctest.py
Original file line number Diff line number Diff line change
Expand Up @@ -665,7 +665,7 @@ def non_Python_modules(): r"""
>>> import builtins
>>> tests = doctest.DocTestFinder().find(builtins)
>>> 800 < len(tests) < 820 # approximate number of objects with docstrings
>>> 810 < len(tests) < 830 # approximate number of objects with docstrings
True
>>> real_tests = [t for t in tests if len(t.examples) > 0]
>>> len(real_tests) # objects that actually have doctests
Expand Down
Loading

0 comments on commit 1a7e7fd

Please sign in to comment.