Skip to content

Commit 8dbbbcd

Browse files
committed
gh-129250: allow pickle instances of generic classes
1 parent 97b0ef0 commit 8dbbbcd

File tree

6 files changed

+287
-25
lines changed

6 files changed

+287
-25
lines changed

Include/internal/pycore_typevarobject.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ extern PyObject *_Py_set_typeparam_default(PyThreadState *, PyObject *, PyObject
1717
extern int _Py_initialize_generic(PyInterpreterState *);
1818
extern void _Py_clear_generic_types(PyInterpreterState *);
1919
extern int _Py_typing_type_repr(PyUnicodeWriter *, PyObject *);
20+
extern int _Py_set_type_params_owner_maybe(PyObject *, PyObject *);
2021

2122
extern PyTypeObject _PyTypeAlias_Type;
2223
extern PyTypeObject _PyNoDefault_Type;

Lib/test/test_type_params.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1183,11 +1183,14 @@ def func1[X](x: X) -> X: ...
11831183
def func2[X, Y](x: X | Y) -> X | Y: ...
11841184
def func3[X, *Y, **Z](x: X, y: tuple[*Y], z: Z) -> X: ...
11851185
def func4[X: int, Y: (bytes, str)](x: X, y: Y) -> X | Y: ...
1186+
def func3b[X, *Y, **Z]() -> X: return Class3[X, Y, Z]()
1187+
def func5[Baz](): return Class5[Baz]()
11861188

11871189
class Class1[X]: ...
11881190
class Class2[X, Y]: ...
11891191
class Class3[X, *Y, **Z]: ...
11901192
class Class4[X: int, Y: (bytes, str)]: ...
1193+
class Class5(Generic[T]): pass
11911194

11921195

11931196
class TypeParamsPickleTest(unittest.TestCase):
@@ -1240,6 +1243,32 @@ def test_pickling_classes(self):
12401243
# but class check is good enough:
12411244
self.assertIsInstance(pickle.loads(pickled), real_class)
12421245

1246+
def test_pickling_anonymous_typeparams(self):
1247+
# see gh-129250
1248+
thing = func5()
1249+
pickled = pickle.dumps(thing)
1250+
unpickled = pickle.loads(pickled)
1251+
self.assertIs(unpickled.__orig_class__, thing.__orig_class__)
1252+
self.assertIs(unpickled.__orig_class__.__args__[0],
1253+
func5.__type_params__[0])
1254+
1255+
thing = func3b()
1256+
pickled = pickle.dumps(thing)
1257+
unpickled = pickle.loads(pickled)
1258+
self.assertIs(unpickled.__orig_class__, thing.__orig_class__)
1259+
self.assertIs(unpickled.__orig_class__.__args__[0],
1260+
func3b.__type_params__[0])
1261+
self.assertIs(unpickled.__orig_class__.__args__[1],
1262+
func3b.__type_params__[1])
1263+
self.assertIs(unpickled.__orig_class__.__args__[2],
1264+
func3b.__type_params__[2])
1265+
1266+
for i in range(3):
1267+
thing = Class3.__type_params__[i]
1268+
pickled = pickle.dumps(thing)
1269+
unpickled = pickle.loads(pickled)
1270+
self.assertIs(unpickled, thing)
1271+
12431272

12441273
class TypeParamsWeakRefTest(unittest.TestCase):
12451274
def test_weakrefs(self):

Lib/typing.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,10 @@ def _eval_type(t, globalns, localns, type_params=_sentinel, *, recursive_guard=f
502502
return t
503503

504504

505+
def _restore_anonymous_typeparam(owner, index):
506+
return owner.__type_params__[index]
507+
508+
505509
class _Final:
506510
"""Mixin to prohibit subclassing."""
507511

Objects/funcobject.c

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
/* Function object implementation */
33

44
#include "Python.h"
5-
#include "pycore_dict.h" // _Py_INCREF_DICT()
6-
#include "pycore_long.h" // _PyLong_GetOne()
7-
#include "pycore_modsupport.h" // _PyArg_NoKeywords()
8-
#include "pycore_object.h" // _PyObject_GC_UNTRACK()
9-
#include "pycore_pyerrors.h" // _PyErr_Occurred()
5+
#include "pycore_dict.h" // _Py_INCREF_DICT()
6+
#include "pycore_long.h" // _PyLong_GetOne()
7+
#include "pycore_modsupport.h" // _PyArg_NoKeywords()
8+
#include "pycore_object.h" // _PyObject_GC_UNTRACK()
9+
#include "pycore_pyerrors.h" // _PyErr_Occurred()
10+
#include "pycore_typevarobject.h" // _Py_set_type_params_owner_maybe()
1011

1112

1213
static const char *
@@ -926,6 +927,7 @@ _Py_set_function_type_params(PyThreadState *Py_UNUSED(ignored), PyObject *func,
926927
assert(PyFunction_Check(func));
927928
assert(PyTuple_Check(type_params));
928929
PyFunctionObject *f = (PyFunctionObject *)func;
930+
_Py_set_type_params_owner_maybe(type_params, func);
929931
Py_XSETREF(f->func_typeparams, Py_NewRef(type_params));
930932
return Py_NewRef(func);
931933
}

0 commit comments

Comments
 (0)