Skip to content

Commit 8f1f3b9

Browse files
gh-104600: Make type.__type_params__ writable (#104634)
Co-authored-by: Alex Waygood <[email protected]>
1 parent dbe171e commit 8f1f3b9

File tree

4 files changed

+71
-15
lines changed

4 files changed

+71
-15
lines changed

Lib/test/test_builtin.py

+12
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import sys
1919
import traceback
2020
import types
21+
import typing
2122
import unittest
2223
import warnings
2324
from contextlib import ExitStack
@@ -2485,6 +2486,17 @@ def test_type_qualname(self):
24852486
A.__qualname__ = b'B'
24862487
self.assertEqual(A.__qualname__, 'D.E')
24872488

2489+
def test_type_typeparams(self):
2490+
class A[T]:
2491+
pass
2492+
T, = A.__type_params__
2493+
self.assertIsInstance(T, typing.TypeVar)
2494+
A.__type_params__ = "whatever"
2495+
self.assertEqual(A.__type_params__, "whatever")
2496+
with self.assertRaises(TypeError):
2497+
del A.__type_params__
2498+
self.assertEqual(A.__type_params__, "whatever")
2499+
24882500
def test_type_doc(self):
24892501
for doc in 'x', '\xc4', '\U0001f40d', 'x\x00y', b'x', 42, None:
24902502
A = type('A', (), {'__doc__': doc})

Lib/test/test_type_params.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -816,10 +816,11 @@ def test_typeparams_dunder_class_03(self):
816816
class ClassA[A]():
817817
pass
818818
ClassA.__type_params__ = ()
819+
params = ClassA.__type_params__
819820
"""
820821

821-
with self.assertRaisesRegex(AttributeError, "attribute '__type_params__' of 'type' objects is not writable"):
822-
run_code(code)
822+
ns = run_code(code)
823+
self.assertEqual(ns["params"], ())
823824

824825
def test_typeparams_dunder_function_01(self):
825826
def outer[A, B]():

Lib/test/test_typing.py

+27
Original file line numberDiff line numberDiff line change
@@ -6810,6 +6810,19 @@ class Y(Generic[T], NamedTuple):
68106810
with self.assertRaises(TypeError):
68116811
G[int, str]
68126812

6813+
def test_generic_pep695(self):
6814+
class X[T](NamedTuple):
6815+
x: T
6816+
T, = X.__type_params__
6817+
self.assertIsInstance(T, TypeVar)
6818+
self.assertEqual(T.__name__, 'T')
6819+
self.assertEqual(X.__bases__, (tuple, Generic))
6820+
self.assertEqual(X.__orig_bases__, (NamedTuple, Generic[T]))
6821+
self.assertEqual(X.__mro__, (X, tuple, Generic, object))
6822+
self.assertEqual(X.__parameters__, (T,))
6823+
self.assertEqual(X[str].__args__, (str,))
6824+
self.assertEqual(X[str].__parameters__, ())
6825+
68136826
def test_non_generic_subscript(self):
68146827
# For backward compatibility, subscription works
68156828
# on arbitrary NamedTuple types.
@@ -7220,6 +7233,20 @@ class FooBarGeneric(BarGeneric[int]):
72207233
{'a': typing.Optional[T], 'b': int, 'c': str}
72217234
)
72227235

7236+
def test_pep695_generic_typeddict(self):
7237+
class A[T](TypedDict):
7238+
a: T
7239+
7240+
T, = A.__type_params__
7241+
self.assertIsInstance(T, TypeVar)
7242+
self.assertEqual(T.__name__, 'T')
7243+
self.assertEqual(A.__bases__, (Generic, dict))
7244+
self.assertEqual(A.__orig_bases__, (TypedDict, Generic[T]))
7245+
self.assertEqual(A.__mro__, (A, Generic, dict, object))
7246+
self.assertEqual(A.__parameters__, (T,))
7247+
self.assertEqual(A[str].__parameters__, ())
7248+
self.assertEqual(A[str].__args__, (str,))
7249+
72237250
def test_generic_inheritance(self):
72247251
class A(TypedDict, Generic[T]):
72257252
a: T

Objects/typeobject.c

+29-13
Original file line numberDiff line numberDiff line change
@@ -1460,18 +1460,6 @@ type_get_annotations(PyTypeObject *type, void *context)
14601460
return annotations;
14611461
}
14621462

1463-
static PyObject *
1464-
type_get_type_params(PyTypeObject *type, void *context)
1465-
{
1466-
PyObject *params = PyDict_GetItem(lookup_tp_dict(type), &_Py_ID(__type_params__));
1467-
1468-
if (params) {
1469-
return Py_NewRef(params);
1470-
}
1471-
1472-
return PyTuple_New(0);
1473-
}
1474-
14751463
static int
14761464
type_set_annotations(PyTypeObject *type, PyObject *value, void *context)
14771465
{
@@ -1502,6 +1490,34 @@ type_set_annotations(PyTypeObject *type, PyObject *value, void *context)
15021490
return result;
15031491
}
15041492

1493+
static PyObject *
1494+
type_get_type_params(PyTypeObject *type, void *context)
1495+
{
1496+
PyObject *params = PyDict_GetItem(lookup_tp_dict(type), &_Py_ID(__type_params__));
1497+
1498+
if (params) {
1499+
return Py_NewRef(params);
1500+
}
1501+
1502+
return PyTuple_New(0);
1503+
}
1504+
1505+
static int
1506+
type_set_type_params(PyTypeObject *type, PyObject *value, void *context)
1507+
{
1508+
if (!check_set_special_type_attr(type, value, "__type_params__")) {
1509+
return -1;
1510+
}
1511+
1512+
PyObject *dict = lookup_tp_dict(type);
1513+
int result = PyDict_SetItem(dict, &_Py_ID(__type_params__), value);
1514+
1515+
if (result == 0) {
1516+
PyType_Modified(type);
1517+
}
1518+
return result;
1519+
}
1520+
15051521

15061522
/*[clinic input]
15071523
type.__instancecheck__ -> bool
@@ -1548,7 +1564,7 @@ static PyGetSetDef type_getsets[] = {
15481564
{"__doc__", (getter)type_get_doc, (setter)type_set_doc, NULL},
15491565
{"__text_signature__", (getter)type_get_text_signature, NULL, NULL},
15501566
{"__annotations__", (getter)type_get_annotations, (setter)type_set_annotations, NULL},
1551-
{"__type_params__", (getter)type_get_type_params, NULL, NULL},
1567+
{"__type_params__", (getter)type_get_type_params, (setter)type_set_type_params, NULL},
15521568
{0}
15531569
};
15541570

0 commit comments

Comments
 (0)