| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -264,6 +264,24 @@ Type Objects | |||
| 264 | 264 | ||
| 265 | 265 | .. versionadded:: 3.11 | |
| 266 | 266 | ||
| 267 | + .. c:function:: int PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) | ||
| 268 | + | ||
| 269 | + Find the first superclass in *type*'s :term:`method resolution order` whose | ||
| 270 | + :c:macro:`Py_tp_token` token is equal to the given one. | ||
| 271 | + | ||
| 272 | + * If found, set *\*result* to a new :term:`strong reference` | ||
| 273 | + to it and return ``1``. | ||
| 274 | + * If not found, set *\*result* to ``NULL`` and return ``0``. | ||
| 275 | + * On error, set *\*result* to ``NULL`` and return ``-1`` with an | ||
| 276 | + exception set. | ||
| 277 | + | ||
| 278 | + The *result* argument may be ``NULL``, in which case *\*result* is not set. | ||
| 279 | + Use this if you need only the return value. | ||
| 280 | + | ||
| 281 | + The *token* argument may not be ``NULL``. | ||
| 282 | + | ||
| 283 | + .. versionadded:: 3.14 | ||
| 284 | + | ||
| 267 | 285 | .. c:function:: int PyUnstable_Type_AssignVersionTag(PyTypeObject *type) | |
| 268 | 286 | ||
| 269 | 287 | Attempt to assign a version tag to the given type. | |
@@ -488,6 +506,11 @@ The following functions and structs are used to create | |||
| 488 | 506 | * ``Py_nb_add`` to set :c:member:`PyNumberMethods.nb_add` | |
| 489 | 507 | * ``Py_sq_length`` to set :c:member:`PySequenceMethods.sq_length` | |
| 490 | 508 | ||
| 509 | + An additional slot is supported that does not correspond to a | ||
| 510 | + :c:type:`!PyTypeObject` struct field: | ||
| 511 | + | ||
| 512 | + * :c:data:`Py_tp_token` | ||
| 513 | + | ||
| 491 | 514 | The following “offset” fields cannot be set using :c:type:`PyType_Slot`: | |
| 492 | 515 | ||
| 493 | 516 | * :c:member:`~PyTypeObject.tp_weaklistoffset` | |
@@ -538,4 +561,47 @@ The following functions and structs are used to create | |||
| 538 | 561 | The desired value of the slot. In most cases, this is a pointer | |
| 539 | 562 | to a function. | |
| 540 | 563 | ||
| 541 | - Slots other than ``Py_tp_doc`` may not be ``NULL``. | ||
| 564 | + *pfunc* values may not be ``NULL``, except for the following slots: | ||
| 565 | + | ||
| 566 | + * ``Py_tp_doc`` | ||
| 567 | + * :c:data:`Py_tp_token` (for clarity, prefer :c:data:`Py_TP_USE_SPEC` | ||
| 568 | + rather than ``NULL``) | ||
| 569 | + | ||
| 570 | + .. c:macro:: Py_tp_token | ||
| 571 | + | ||
| 572 | + A :c:member:`~PyType_Slot.slot` that records a static memory layout ID | ||
| 573 | + for a class. | ||
| 574 | + | ||
| 575 | + If the :c:type:`PyType_Spec` of the class is statically | ||
| 576 | + allocated, the token can be set to the spec using the special value | ||
| 577 | + :c:data:`Py_TP_USE_SPEC`: | ||
| 578 | + | ||
| 579 | + .. code-block:: c | ||
| 580 | + | ||
| 581 | + static PyType_Slot foo_slots[] = { | ||
| 582 | + {Py_tp_token, Py_TP_USE_SPEC}, | ||
| 583 | + | ||
| 584 | + It can also be set to an arbitrary pointer, but you must ensure that: | ||
| 585 | + | ||
| 586 | + * The pointer outlives the class, so it's not reused for something else | ||
| 587 | + while the class exists. | ||
| 588 | + * It "belongs" to the extension module where the class lives, so it will not | ||
| 589 | + clash with other extensions. | ||
| 590 | + | ||
| 591 | + Use :c:func:`PyType_GetBaseByToken` to check if a class's superclass has | ||
| 592 | + a given token -- that is, check whether the memory layout is compatible. | ||
| 593 | + | ||
| 594 | + To get the token for a given class (without considering superclasses), | ||
| 595 | + use :c:func:`PyType_GetSlot` with ``Py_tp_token``. | ||
| 596 | + | ||
| 597 | + .. versionadded:: 3.14 | ||
| 598 | + | ||
| 599 | + .. c:namespace:: NULL | ||
| 600 | + | ||
| 601 | + .. c:macro:: Py_TP_USE_SPEC | ||
| 602 | + | ||
| 603 | + Used as a value with :c:data:`Py_tp_token` to set the token to the | ||
| 604 | + class's :c:type:`PyType_Spec`. | ||
| 605 | + Expands to ``NULL``. | ||
| 606 | + | ||
| 607 | + .. versionadded:: 3.14 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -554,6 +554,11 @@ New Features | |||
| 554 | 554 | ||
| 555 | 555 | (Contributed by Victor Stinner in :gh:`107954`.) | |
| 556 | 556 | ||
| 557 | + * Add :c:func:`PyType_GetBaseByToken` and :c:data:`Py_tp_token` slot for easier | ||
| 558 | + superclass identification, which attempts to resolve the `type checking issue | ||
| 559 | + <https://peps.python.org/pep-0630/#type-checking>`__ mentioned in :pep:`630` | ||
| 560 | + (:gh:`124153`). | ||
| 561 | + | ||
| 557 | 562 | ||
| 558 | 563 | Porting to Python 3.14 | |
| 559 | 564 | ---------------------- | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -269,6 +269,7 @@ typedef struct _heaptypeobject { | |||
| 269 | 269 | struct _dictkeysobject *ht_cached_keys; | |
| 270 | 270 | PyObject *ht_module; | |
| 271 | 271 | char *_ht_tpname; // Storage for "tp_name"; see PyType_FromModuleAndSpec | |
| 272 | + void *ht_token; // Storage for the "Py_tp_token" slot | ||
| 272 | 273 | struct _specialization_cache _spec_cache; // For use by the specializer. | |
| 273 | 274 | #ifdef Py_GIL_DISABLED | |
| 274 | 275 | Py_ssize_t unique_id; // ID used for thread-local refcounting | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -391,6 +391,10 @@ PyAPI_FUNC(PyObject *) PyType_FromMetaclass(PyTypeObject*, PyObject*, PyType_Spe | |||
| 391 | 391 | PyAPI_FUNC(void *) PyObject_GetTypeData(PyObject *obj, PyTypeObject *cls); | |
| 392 | 392 | PyAPI_FUNC(Py_ssize_t) PyType_GetTypeDataSize(PyTypeObject *cls); | |
| 393 | 393 | #endif | |
| 394 | + #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030E0000 | ||
| 395 | + PyAPI_FUNC(int) PyType_GetBaseByToken(PyTypeObject *, void *, PyTypeObject **); | ||
| 396 | + #define Py_TP_USE_SPEC NULL | ||
| 397 | + #endif | ||
| 394 | 398 | ||
| 395 | 399 | /* Generic type check */ | |
| 396 | 400 | PyAPI_FUNC(int) PyType_IsSubtype(PyTypeObject *, PyTypeObject *); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -90,3 +90,7 @@ | |||
| 90 | 90 | /* New in 3.14 */ | |
| 91 | 91 | #define Py_tp_vectorcall 82 | |
| 92 | 92 | #endif | |
| 93 | + #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030E0000 | ||
| 94 | + /* New in 3.14 */ | ||
| 95 | + #define Py_tp_token 83 | ||
| 96 | + #endif | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1144,6 +1144,77 @@ class MyType: | |||
| 1144 | 1144 | MyType.__module__ = 123 | |
| 1145 | 1145 | self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname') | |
| 1146 | 1146 | ||
| 1147 | + def test_get_base_by_token(self): | ||
| 1148 | + def get_base_by_token(src, key, comparable=True): | ||
| 1149 | + def run(use_mro): | ||
| 1150 | + find_first = _testcapi.pytype_getbasebytoken | ||
| 1151 | + ret1, result = find_first(src, key, use_mro, True) | ||
| 1152 | + ret2, no_result = find_first(src, key, use_mro, False) | ||
| 1153 | + self.assertIn(ret1, (0, 1)) | ||
| 1154 | + self.assertEqual(ret1, result is not None) | ||
| 1155 | + self.assertEqual(ret1, ret2) | ||
| 1156 | + self.assertIsNone(no_result) | ||
| 1157 | + return result | ||
| 1158 | + | ||
| 1159 | + found_in_mro = run(True) | ||
| 1160 | + found_in_bases = run(False) | ||
| 1161 | + if comparable: | ||
| 1162 | + self.assertIs(found_in_mro, found_in_bases) | ||
| 1163 | + return found_in_mro | ||
| 1164 | + return found_in_mro, found_in_bases | ||
| 1165 | + | ||
| 1166 | + create_type = _testcapi.create_type_with_token | ||
| 1167 | + get_token = _testcapi.get_tp_token | ||
| 1168 | + | ||
| 1169 | + Py_TP_USE_SPEC = _testcapi.Py_TP_USE_SPEC | ||
| 1170 | + self.assertEqual(Py_TP_USE_SPEC, 0) | ||
| 1171 | + | ||
| 1172 | + A1 = create_type('_testcapi.A1', Py_TP_USE_SPEC) | ||
| 1173 | + self.assertTrue(get_token(A1) != Py_TP_USE_SPEC) | ||
| 1174 | + | ||
| 1175 | + B1 = create_type('_testcapi.B1', id(self)) | ||
| 1176 | + self.assertTrue(get_token(B1) == id(self)) | ||
| 1177 | + | ||
| 1178 | + tokenA1 = get_token(A1) | ||
| 1179 | + # find A1 from A1 | ||
| 1180 | + found = get_base_by_token(A1, tokenA1) | ||
| 1181 | + self.assertIs(found, A1) | ||
| 1182 | + | ||
| 1183 | + # no token in static types | ||
| 1184 | + STATIC = type(1) | ||
| 1185 | + self.assertEqual(get_token(STATIC), 0) | ||
| 1186 | + found = get_base_by_token(STATIC, tokenA1) | ||
| 1187 | + self.assertIs(found, None) | ||
| 1188 | + | ||
| 1189 | + # no token in pure subtypes | ||
| 1190 | + class A2(A1): pass | ||
| 1191 | + self.assertEqual(get_token(A2), 0) | ||
| 1192 | + # find A1 | ||
| 1193 | + class Z(STATIC, B1, A2): pass | ||
| 1194 | + found = get_base_by_token(Z, tokenA1) | ||
| 1195 | + self.assertIs(found, A1) | ||
| 1196 | + | ||
| 1197 | + # searching for NULL token is an error | ||
| 1198 | + with self.assertRaises(SystemError): | ||
| 1199 | + get_base_by_token(Z, 0) | ||
| 1200 | + with self.assertRaises(SystemError): | ||
| 1201 | + get_base_by_token(STATIC, 0) | ||
| 1202 | + | ||
| 1203 | + # share the token with A1 | ||
| 1204 | + C1 = create_type('_testcapi.C1', tokenA1) | ||
| 1205 | + self.assertTrue(get_token(C1) == tokenA1) | ||
| 1206 | + | ||
| 1207 | + # find C1 first by shared token | ||
| 1208 | + class Z(C1, A2): pass | ||
| 1209 | + found = get_base_by_token(Z, tokenA1) | ||
| 1210 | + self.assertIs(found, C1) | ||
| 1211 | + # B1 not found | ||
| 1212 | + found = get_base_by_token(Z, get_token(B1)) | ||
| 1213 | + self.assertIs(found, None) | ||
| 1214 | + | ||
| 1215 | + with self.assertRaises(TypeError): | ||
| 1216 | + _testcapi.pytype_getbasebytoken( | ||
| 1217 | + 'not a type', id(self), True, False) | ||
| 1147 | 1218 | ||
| 1148 | 1219 | def test_gen_get_code(self): | |
| 1149 | 1220 | def genf(): yield | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1718,7 +1718,7 @@ def delx(self): del self.__x | |||
| 1718 | 1718 | '3P' # PyMappingMethods | |
| 1719 | 1719 | '10P' # PySequenceMethods | |
| 1720 | 1720 | '2P' # PyBufferProcs | |
| 1721 | - '6P' | ||
| 1721 | + '7P' | ||
| 1722 | 1722 | '1PIP' # Specializer cache | |
| 1723 | 1723 | + typeid # heap type id (free-threaded only) | |
| 1724 | 1724 | ) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,2 @@ | |||
| 1 | + Add :c:func:`PyType_GetBaseByToken` and :c:data:`Py_tp_token` slot for easier | ||
| 2 | + type checking, related to :pep:`489` and :pep:`630`. | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments