Skip to content

Commit 3fadd7d

Browse files
gh-104600: Make function.__type_params__ writable (#104601)
1 parent f7835fc commit 3fadd7d

File tree

6 files changed

+39
-5
lines changed

6 files changed

+39
-5
lines changed

Lib/functools.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
# wrapper functions that can handle naive introspection
3131

3232
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
33-
'__annotations__')
33+
'__annotations__', '__type_params__')
3434
WRAPPER_UPDATES = ('__dict__',)
3535
def update_wrapper(wrapper,
3636
wrapped,

Lib/test/test_funcattrs.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import textwrap
22
import types
3+
import typing
34
import unittest
45

56

@@ -190,6 +191,20 @@ def test___qualname__(self):
190191
# __qualname__ must be a string
191192
self.cannot_set_attr(self.b, '__qualname__', 7, TypeError)
192193

194+
def test___type_params__(self):
195+
def generic[T](): pass
196+
def not_generic(): pass
197+
T, = generic.__type_params__
198+
self.assertIsInstance(T, typing.TypeVar)
199+
self.assertEqual(generic.__type_params__, (T,))
200+
self.assertEqual(not_generic.__type_params__, ())
201+
with self.assertRaises(TypeError):
202+
del not_generic.__type_params__
203+
with self.assertRaises(TypeError):
204+
not_generic.__type_params__ = 42
205+
not_generic.__type_params__ = (T,)
206+
self.assertEqual(not_generic.__type_params__, (T,))
207+
193208
def test___code__(self):
194209
num_one, num_two = 7, 8
195210
def a(): pass

Lib/test/test_functools.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,7 @@ def check_wrapper(self, wrapper, wrapped,
617617

618618

619619
def _default_update(self):
620-
def f(a:'This is a new annotation'):
620+
def f[T](a:'This is a new annotation'):
621621
"""This is a test"""
622622
pass
623623
f.attr = 'This is also a test'
@@ -630,12 +630,14 @@ def wrapper(b:'This is the prior annotation'):
630630
def test_default_update(self):
631631
wrapper, f = self._default_update()
632632
self.check_wrapper(wrapper, f)
633+
T, = f.__type_params__
633634
self.assertIs(wrapper.__wrapped__, f)
634635
self.assertEqual(wrapper.__name__, 'f')
635636
self.assertEqual(wrapper.__qualname__, f.__qualname__)
636637
self.assertEqual(wrapper.attr, 'This is also a test')
637638
self.assertEqual(wrapper.__annotations__['a'], 'This is a new annotation')
638639
self.assertNotIn('b', wrapper.__annotations__)
640+
self.assertEqual(wrapper.__type_params__, (T,))
639641

640642
@unittest.skipIf(sys.flags.optimize >= 2,
641643
"Docstrings are omitted with -O2 and above")

Lib/test/test_type_params.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -843,5 +843,5 @@ def func[A]():
843843
func.__type_params__ = ()
844844
"""
845845

846-
with self.assertRaisesRegex(AttributeError, "attribute '__type_params__' of 'function' objects is not writable"):
847-
run_code(code)
846+
ns = run_code(code)
847+
self.assertEqual(ns["func"].__type_params__, ())
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:func:`functools.update_wrapper` now sets the ``__type_params__`` attribute
2+
(added by :pep:`695`).

Objects/funcobject.c

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,20 @@ func_get_type_params(PyFunctionObject *op, void *Py_UNUSED(ignored))
665665
return Py_NewRef(op->func_typeparams);
666666
}
667667

668+
static int
669+
func_set_type_params(PyFunctionObject *op, PyObject *value, void *Py_UNUSED(ignored))
670+
{
671+
/* Not legal to del f.__type_params__ or to set it to anything
672+
* other than a tuple object. */
673+
if (value == NULL || !PyTuple_Check(value)) {
674+
PyErr_SetString(PyExc_TypeError,
675+
"__type_params__ must be set to a tuple");
676+
return -1;
677+
}
678+
Py_XSETREF(op->func_typeparams, Py_NewRef(value));
679+
return 0;
680+
}
681+
668682
PyObject *
669683
_Py_set_function_type_params(PyThreadState *Py_UNUSED(ignored), PyObject *func,
670684
PyObject *type_params)
@@ -687,7 +701,8 @@ static PyGetSetDef func_getsetlist[] = {
687701
{"__dict__", PyObject_GenericGetDict, PyObject_GenericSetDict},
688702
{"__name__", (getter)func_get_name, (setter)func_set_name},
689703
{"__qualname__", (getter)func_get_qualname, (setter)func_set_qualname},
690-
{"__type_params__", (getter)func_get_type_params, NULL},
704+
{"__type_params__", (getter)func_get_type_params,
705+
(setter)func_set_type_params},
691706
{NULL} /* Sentinel */
692707
};
693708

0 commit comments

Comments
 (0)