Skip to content

Commit 069e81a

Browse files
authored
bpo-43977: Use tp_flags for collection matching (GH-25723)
* Add Py_TPFLAGS_SEQUENCE and Py_TPFLAGS_MAPPING, add to all relevant standard builtin classes. * Set relevant flags on collections.abc.Sequence and Mapping. * Use flags in MATCH_SEQUENCE and MATCH_MAPPING opcodes. * Inherit Py_TPFLAGS_SEQUENCE and Py_TPFLAGS_MAPPING. * Add NEWS * Remove interpreter-state map_abc and seq_abc fields.
1 parent 2abbd8f commit 069e81a

File tree

16 files changed

+74
-83
lines changed

16 files changed

+74
-83
lines changed

Include/internal/pycore_interp.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -247,10 +247,6 @@ struct _is {
247247
// importlib module
248248
PyObject *importlib;
249249

250-
// Kept handy for pattern matching:
251-
PyObject *map_abc; // _collections_abc.Mapping
252-
PyObject *seq_abc; // _collections_abc.Sequence
253-
254250
/* Used in Modules/_threadmodule.c. */
255251
long num_threads;
256252
/* Support for runtime thread stack size tuning.

Include/object.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,13 @@ Code can use PyType_HasFeature(type_ob, flag_value) to test whether the
320320
given type object has a specified feature.
321321
*/
322322

323+
#ifndef Py_LIMITED_API
324+
/* Set if instances of the type object are treated as sequences for pattern matching */
325+
#define Py_TPFLAGS_SEQUENCE (1 << 5)
326+
/* Set if instances of the type object are treated as mappings for pattern matching */
327+
#define Py_TPFLAGS_MAPPING (1 << 6)
328+
#endif
329+
323330
/* Set if the type object is immutable: type attributes cannot be set nor deleted */
324331
#define Py_TPFLAGS_IMMUTABLETYPE (1UL << 8)
325332

Lib/_collections_abc.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -793,7 +793,6 @@ def __isub__(self, it):
793793

794794
### MAPPINGS ###
795795

796-
797796
class Mapping(Collection):
798797
"""A Mapping is a generic container for associating key/value
799798
pairs.
@@ -804,6 +803,9 @@ class Mapping(Collection):
804803

805804
__slots__ = ()
806805

806+
# Tell ABCMeta.__new__ that this class should have TPFLAGS_MAPPING set.
807+
__abc_tpflags__ = 1 << 6 # Py_TPFLAGS_MAPPING
808+
807809
@abstractmethod
808810
def __getitem__(self, key):
809811
raise KeyError
@@ -842,7 +844,6 @@ def __eq__(self, other):
842844

843845
__reversed__ = None
844846

845-
846847
Mapping.register(mappingproxy)
847848

848849

@@ -1011,7 +1012,6 @@ def setdefault(self, key, default=None):
10111012

10121013
### SEQUENCES ###
10131014

1014-
10151015
class Sequence(Reversible, Collection):
10161016
"""All the operations on a read-only sequence.
10171017
@@ -1021,6 +1021,9 @@ class Sequence(Reversible, Collection):
10211021

10221022
__slots__ = ()
10231023

1024+
# Tell ABCMeta.__new__ that this class should have TPFLAGS_SEQUENCE set.
1025+
__abc_tpflags__ = 1 << 5 # Py_TPFLAGS_SEQUENCE
1026+
10241027
@abstractmethod
10251028
def __getitem__(self, index):
10261029
raise IndexError
@@ -1072,7 +1075,6 @@ def count(self, value):
10721075
'S.count(value) -> integer -- return number of occurrences of value'
10731076
return sum(1 for v in self if v is value or v == value)
10741077

1075-
10761078
Sequence.register(tuple)
10771079
Sequence.register(str)
10781080
Sequence.register(range)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Use :c:member:`~PyTypeObject.tp_flags` on the class object to determine if the subject is a sequence
2+
or mapping when pattern matching. Avoids the need to import :mod:`collections.abc` when pattern matching.

Modules/_abc.c

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ PyDoc_STRVAR(_abc__doc__,
1515
_Py_IDENTIFIER(__abstractmethods__);
1616
_Py_IDENTIFIER(__class__);
1717
_Py_IDENTIFIER(__dict__);
18+
_Py_IDENTIFIER(__abc_tpflags__);
1819
_Py_IDENTIFIER(__bases__);
1920
_Py_IDENTIFIER(_abc_impl);
2021
_Py_IDENTIFIER(__subclasscheck__);
@@ -417,6 +418,8 @@ compute_abstract_methods(PyObject *self)
417418
return ret;
418419
}
419420

421+
#define COLLECTION_FLAGS (Py_TPFLAGS_SEQUENCE | Py_TPFLAGS_MAPPING)
422+
420423
/*[clinic input]
421424
_abc._abc_init
422425
@@ -446,6 +449,31 @@ _abc__abc_init(PyObject *module, PyObject *self)
446449
return NULL;
447450
}
448451
Py_DECREF(data);
452+
/* If __abc_tpflags__ & COLLECTION_FLAGS is set, then set the corresponding bit(s)
453+
* in the new class.
454+
* Used by collections.abc.Sequence and collections.abc.Mapping to indicate
455+
* their special status w.r.t. pattern matching. */
456+
if (PyType_Check(self)) {
457+
PyTypeObject *cls = (PyTypeObject *)self;
458+
PyObject *flags = _PyDict_GetItemIdWithError(cls->tp_dict, &PyId___abc_tpflags__);
459+
if (flags == NULL) {
460+
if (PyErr_Occurred()) {
461+
return NULL;
462+
}
463+
}
464+
else {
465+
if (PyLong_CheckExact(flags)) {
466+
long val = PyLong_AsLong(flags);
467+
if (val == -1 && PyErr_Occurred()) {
468+
return NULL;
469+
}
470+
((PyTypeObject *)self)->tp_flags |= (val & COLLECTION_FLAGS);
471+
}
472+
if (_PyDict_DelItemId(cls->tp_dict, &PyId___abc_tpflags__) < 0) {
473+
return NULL;
474+
}
475+
}
476+
}
449477
Py_RETURN_NONE;
450478
}
451479

@@ -499,6 +527,11 @@ _abc__abc_register_impl(PyObject *module, PyObject *self, PyObject *subclass)
499527
/* Invalidate negative cache */
500528
get_abc_state(module)->abc_invalidation_counter++;
501529

530+
if (PyType_Check(subclass) && PyType_Check(self) &&
531+
!PyType_HasFeature((PyTypeObject *)subclass, Py_TPFLAGS_IMMUTABLETYPE))
532+
{
533+
((PyTypeObject *)subclass)->tp_flags |= (((PyTypeObject *)self)->tp_flags & COLLECTION_FLAGS);
534+
}
502535
Py_INCREF(subclass);
503536
return subclass;
504537
}

Modules/_collectionsmodule.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1662,7 +1662,8 @@ static PyTypeObject deque_type = {
16621662
PyObject_GenericGetAttr, /* tp_getattro */
16631663
0, /* tp_setattro */
16641664
0, /* tp_as_buffer */
1665-
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
1665+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |
1666+
Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_SEQUENCE,
16661667
/* tp_flags */
16671668
deque_doc, /* tp_doc */
16681669
(traverseproc)deque_traverse, /* tp_traverse */

Modules/arraymodule.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2848,7 +2848,8 @@ static PyType_Spec array_spec = {
28482848
.name = "array.array",
28492849
.basicsize = sizeof(arrayobject),
28502850
.flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |
2851-
Py_TPFLAGS_IMMUTABLETYPE),
2851+
Py_TPFLAGS_IMMUTABLETYPE |
2852+
Py_TPFLAGS_SEQUENCE),
28522853
.slots = array_slots,
28532854
};
28542855

Objects/descrobject.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1852,7 +1852,8 @@ PyTypeObject PyDictProxy_Type = {
18521852
PyObject_GenericGetAttr, /* tp_getattro */
18531853
0, /* tp_setattro */
18541854
0, /* tp_as_buffer */
1855-
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */
1855+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
1856+
Py_TPFLAGS_MAPPING, /* tp_flags */
18561857
0, /* tp_doc */
18571858
mappingproxy_traverse, /* tp_traverse */
18581859
0, /* tp_clear */

Objects/dictobject.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3553,7 +3553,7 @@ PyTypeObject PyDict_Type = {
35533553
0, /* tp_as_buffer */
35543554
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
35553555
Py_TPFLAGS_BASETYPE | Py_TPFLAGS_DICT_SUBCLASS |
3556-
_Py_TPFLAGS_MATCH_SELF, /* tp_flags */
3556+
_Py_TPFLAGS_MATCH_SELF | Py_TPFLAGS_MAPPING, /* tp_flags */
35573557
dictionary_doc, /* tp_doc */
35583558
dict_traverse, /* tp_traverse */
35593559
dict_tp_clear, /* tp_clear */

Objects/listobject.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3053,7 +3053,7 @@ PyTypeObject PyList_Type = {
30533053
0, /* tp_as_buffer */
30543054
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
30553055
Py_TPFLAGS_BASETYPE | Py_TPFLAGS_LIST_SUBCLASS |
3056-
_Py_TPFLAGS_MATCH_SELF, /* tp_flags */
3056+
_Py_TPFLAGS_MATCH_SELF | Py_TPFLAGS_SEQUENCE, /* tp_flags */
30573057
list___init____doc__, /* tp_doc */
30583058
(traverseproc)list_traverse, /* tp_traverse */
30593059
(inquiry)_list_clear, /* tp_clear */

0 commit comments

Comments
 (0)