8000 bpo-41559: Convert lists to tuples in genericalias to prepare for PEP 612 by Fidget-Spinner · Pull Request #24056 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

bpo-41559: Convert lists to tuples in genericalias to prepare for PEP 612 #24056

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 12 commits into from
Closed
7 changes: 7 additions & 0 deletions Doc/library/stdtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4946,6 +4946,13 @@ All parameterized generics implement special read-only attributes.
>>> dict[str, list[int]].__args__
(<class 'str'>, list[int])

.. versionchanged:: 3.9.2
``__args__`` will convert all lists passed to the generic alias to tuples.
This aligns with the :mod:`typing` module which tries to ensure that
most types will be hashable for caching purposes. Passing a list is valid
in certain scenarios from Python 3.10 and higher due to :pep:`612`. An
exception to this behavior is the generic alias for
``collections.abc.Callable``.

.. attribute:: genericalias.__parameters__

Expand Down
22 changes: 22 additions & 0 deletions Lib/test/test_genericalias.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,5 +391,27 @@ def __call__(self):
self.assertEqual(repr(C1), "collections.abc.Callable"
"[typing.Concatenate[int, ~P], int]")

def test_tupleify(self):
# Converts all lists inside __args__ to tuple.
# For consistency with typing module which ensures
# most types are hashable. Required since PEP 612
# introduces lists during substitution.

self.assertEqual(tuple[[]], tuple[(),])
self.assertEqual(tuple[int, [str, dict]], tuple[int, (str, dict)])
self.assertEqual(list[int, [str, [str, dict]]], list[int, (str, (str, dict))])
# Most likely to refleak due to nested list inside tuple.
self.assertEqual(list[int, (str, [str, dict])], list[int, (str, (str, dict))])

x = (int, (int, [dict, float]))
self.assertEqual(list[x], list[int, (int, (dict, float))])
# Make sure the original tuple's list wasn't modified.
self.assertEqual(x, (int, (int, [dict, float])))

y = [int, str]
self.assertEqual(list[y], list[[int, str]])
self.assertEqual(y, [int, str])


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:ref:`Generic Alias <types-genericalias>` objects will now convert all lists
received to tuples to 1. work with ``typing.Union`` and ``typing.Optional``,
2. aid caching in other libraries and 3. prepare for :pep:`612` in Python 3.10.
77 changes: 61 additions & 16 deletions Objects/genericaliasobject.c
8000
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "pycore_object.h"
#include "pycore_unionobject.h" // _Py_union_as_number
#include "structmember.h" // PyMemberDef
#include "pycore_tuple.h" // _PyTuple_FromArray()

typedef struct {
PyObject_HEAD
Expand Down Expand Up @@ -225,6 +226,58 @@ tuple_add(PyObject *self, Py_ssize_t len, PyObject *item)
return 0;
}

// Makes a shallow copy of a tuple
static inline PyObject *
tuple_copy(PyObject *obj)
{
return _PyTuple_FromArray(((PyTupleObject *)obj)->ob_item, Py_SIZE(obj));
}

// This converts all nested lists in args to a tuple (args should be a tuple).
// Usually needed to work with typing.py's Union and Optional and help
// caching in other libraries.
static inline PyObject *
tupleify_lists(PyObject *args) {
Py_ssize_t len = PyTuple_GET_SIZE(args);
PyObject *result = tuple_copy(args);
if (result == NULL) {
return NULL;
}
for (Py_ssize_t i = 0; i < len; ++i) {
PyObject *arg = PyTuple_GET_ITEM(result, i);
if (arg == NULL) {
goto error;
}
PyObject *new_arg = arg;
if (PyList_CheckExact(arg)) {
new_arg = PyList_AsTuple(arg);
if (new_arg == NULL) {
goto error;
}
Py_DECREF(arg);
}
else if (!PyTuple_CheckExact(arg)) {
continue;
}
if (Py_EnterRecursiveCall(" while converting lists to tuples in "
"GenericAlias' __args__")) {
goto error;
}
PyObject *new_arg_tupled = tupleify_lists(new_arg);
Py_DECREF(new_arg);
Py_LeaveRecursiveCall();
if (new_arg_tupled == NULL) {
goto error;
}
PyTuple_SET_ITEM(result, i, new_arg_tupled);
}
return result;

error:
Py_DECREF(result);
return NULL;
}

static PyObject *
make_parameters(PyObject *args)
{
Expand Down Expand Up @@ -306,14 +359,7 @@ subs_tvars(PyObject *obj, PyObject *params, PyObject **argitems)
if (iparam >= 0) {
arg = argitems[iparam];
}
// convert all the lists inside args to tuples to help
// with caching in other libaries
if (PyList_CheckExact(arg)) {
arg = PyList_AsTuple(arg);
}
else {
Py_INCREF(arg);
}
Py_INCREF(arg);
PyTuple_SET_ITEM(subargs, i, arg);
}

Expand Down Expand Up @@ -384,13 +430,7 @@ ga_getitem(PyObject *self, PyObject *item)
Py_ssize_t iparam = tuple_index(alias->parameters, nparams, arg);
assert(iparam >= 0);
arg = argitems[iparam];
// convert lists to tuples to help with caching in other libaries.
if (PyList_CheckExact(arg)) {
arg = PyList_AsTuple(arg);
}
else {
Py_INCREF(arg);
}
Py_INCREF(arg);
}
else {
arg = subs_tvars(arg, alias->parameters, argitems);
Expand Down Expand Up @@ -626,10 +666,15 @@ setup_ga(gaobject *alias, PyObject *origin, PyObject *args) {
else {
Py_INCREF(args);
}
PyObject *new_args = tupleify_lists(args);
Py_DECREF(args);
if (new_args == NULL) {
return 0;
}

Py_INCREF(origin);
alias->origin = origin;
alias->args = args;
alias->args = new_args;
alias->parameters = NULL;
alias->weakreflist = NULL;
return 1;
Expand Down
0