Skip to content

Commit 8f1f3b9

Browse files
gh-104600: Make type.__type_params__ writable (#104634)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
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

Lines changed: 12 additions & 0 deletions
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

Lines changed: 3 additions & 2 deletions
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

Lines changed: 27 additions & 0 deletions
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

Lines changed: 29 additions & 13 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy