diff options
author | Hai Shi <shihai1992@gmail.com> | 2020-12-29 20:45:07 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-12-29 04:45:07 -0800 |
commit | dd39123970892733c317f235808638ae5c0ccf04 (patch) | |
tree | 26d3e84894c96f5bc220cad8b6b0a87763b11863 /Modules | |
parent | bpo-42700: Swap descriptions in pyexpat.errors (GH-23876) (diff) | |
download | cpython-dd39123970892733c317f235808638ae5c0ccf04.tar.gz cpython-dd39123970892733c317f235808638ae5c0ccf04.tar.bz2 cpython-dd39123970892733c317f235808638ae5c0ccf04.zip |
bpo-40137: Convert _functools module to use PyType_FromModuleAndSpec. (GH-23405)
Diffstat (limited to 'Modules')
-rw-r--r-- | Modules/_functoolsmodule.c | 475 |
1 files changed, 253 insertions, 222 deletions
diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index 621b721d011..b121ec7d141 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -24,9 +24,37 @@ typedef struct { vectorcallfunc vectorcall; } partialobject; -static PyTypeObject partial_type; +typedef struct _functools_state { + /* this object is used delimit args and keywords in the cache keys */ + PyObject *kwd_mark; + PyTypeObject *partial_type; + PyTypeObject *keyobject_type; + PyTypeObject *lru_list_elem_type; +} _functools_state; + +static inline _functools_state * +get_functools_state(PyObject *module) +{ + void *state = PyModule_GetState(module); + assert(state != NULL); + return (_functools_state *)state; +} static void partial_setvectorcall(partialobject *pto); +static struct PyModuleDef _functools_module; +static PyObject * +partial_call(partialobject *pto, PyObject *args, PyObject *kwargs); + +static inline _functools_state * +get_functools_state_by_type(PyTypeObject *type) +{ + PyObject *module = _PyType_GetModuleByDef(type, &_functools_module); + if (module == NULL) { + return NULL; + } + _functools_state *state = get_functools_state(module); + return state; +} static PyObject * partial_new(PyTypeObject *type, PyObject *args, PyObject *kw) @@ -42,7 +70,11 @@ partial_new(PyTypeObject *type, PyObject *args, PyObject *kw) pargs = pkw = NULL; func = PyTuple_GET_ITEM(args, 0); - if (Py_IS_TYPE(func, &partial_type) && type == &partial_type) { + if (Py_TYPE(func)->tp_call == (ternaryfunc)partial_call) { + // The type of "func" might not be exactly the same type object + // as "type", but if it is called using partial_call, it must have the + // same memory layout (fn, args and kw members). + // We can use its underlying function directly and merge the arguments. partialobject *part = (partialobject *)func; if (part->dict == NULL) { pargs = part->args; @@ -117,6 +149,7 @@ partial_new(PyTypeObject *type, PyObject *args, PyObject *kw) static void partial_dealloc(partialobject *pto) { + PyTypeObject *tp = Py_TYPE(pto); /* bpo-31095: UnTrack is needed before calling any callbacks */ PyObject_GC_UnTrack(pto); if (pto->weakreflist != NULL) @@ -125,7 +158,8 @@ partial_dealloc(partialobject *pto) Py_XDECREF(pto->args); Py_XDECREF(pto->kw); Py_XDECREF(pto->dict); - Py_TYPE(pto)->tp_free(pto); + tp->tp_free(pto); + Py_DECREF(tp); } @@ -294,6 +328,12 @@ static PyMemberDef partial_memberlist[] = { "tuple of arguments to future partial calls"}, {"keywords", T_OBJECT, OFF(kw), READONLY, "dictionary of keyword arguments to future partial calls"}, + {"__weaklistoffset__", T_PYSSIZET, + offsetof(partialobject, weakreflist), READONLY}, + {"__dictoffset__", T_PYSSIZET, + offsetof(partialobject, dict), READONLY}, + {"__vectorcalloffset__", T_PYSSIZET, + offsetof(partialobject, vectorcall), READONLY}, {NULL} /* Sentinel */ }; @@ -420,49 +460,28 @@ static PyMethodDef partial_methods[] = { {NULL, NULL} /* sentinel */ }; -static PyTypeObject partial_type = { - PyVarObject_HEAD_INIT(NULL, 0) - "functools.partial", /* tp_name */ - sizeof(partialobject), /* tp_basicsize */ - 0, /* tp_itemsize */ - /* methods */ - (destructor)partial_dealloc, /* tp_dealloc */ - offsetof(partialobject, vectorcall),/* tp_vectorcall_offset */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_as_async */ - (reprfunc)partial_repr, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - (ternaryfunc)partial_call, /* tp_call */ - 0, /* tp_str */ - PyObject_GenericGetAttr, /* tp_getattro */ - PyObject_GenericSetAttr, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | - Py_TPFLAGS_BASETYPE | - Py_TPFLAGS_HAVE_VECTORCALL, /* tp_flags */ - partial_doc, /* tp_doc */ - (traverseproc)partial_traverse, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - offsetof(partialobject, weakreflist), /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - partial_methods, /* tp_methods */ - partial_memberlist, /* tp_members */ - partial_getsetlist, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - offsetof(partialobject, dict), /* tp_dictoffset */ - 0, /* tp_init */ - 0, /* tp_alloc */ - partial_new, /* tp_new */ - PyObject_GC_Del, /* tp_free */ +static PyType_Slot partial_type_slots[] = { + {Py_tp_dealloc, partial_dealloc}, + {Py_tp_repr, partial_repr}, + {Py_tp_call, partial_call}, + {Py_tp_getattro, PyObject_GenericGetAttr}, + {Py_tp_setattro, PyObject_GenericSetAttr}, + {Py_tp_doc, (void *)partial_doc}, + {Py_tp_traverse, partial_traverse}, + {Py_tp_methods, partial_methods}, + {Py_tp_members, partial_memberlist}, + {Py_tp_getset, partial_getsetlist}, + {Py_tp_new, partial_new}, + {Py_tp_free, PyObject_GC_Del}, + {0, 0} +}; + +static PyType_Spec partial_type_spec = { + .name = "functools.partial", + .basicsize = sizeof(partialobject), + .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | + Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_VECTORCALL, + .slots = partial_type_slots }; @@ -474,12 +493,21 @@ typedef struct { PyObject *object; } keyobject; +static int +keyobject_clear(keyobject *ko) +{ + Py_CLEAR(ko->cmp); + Py_CLEAR(ko->object); + return 0; +} + static void keyobject_dealloc(keyobject *ko) { - Py_DECREF(ko->cmp); - Py_XDECREF(ko->object); + PyTypeObject *tp = Py_TYPE(ko); + keyobject_clear(ko); PyObject_Free(ko); + Py_DECREF(tp); } static int @@ -490,15 +518,6 @@ keyobject_traverse(keyobject *ko, visitproc visit, void *arg) return 0; } -static int -keyobject_clear(keyobject *ko) -{ - Py_CLEAR(ko->cmp); - if (ko->object) - Py_CLEAR(ko->object); - return 0; -} - static PyMemberDef keyobject_members[] = { {"obj", T_OBJECT, offsetof(keyobject, object), 0, @@ -512,38 +531,22 @@ keyobject_call(keyobject *ko, PyObject *args, PyObject *kwds); static PyObject * keyobject_richcompare(PyObject *ko, PyObject *other, int op); -static PyTypeObject keyobject_type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - "functools.KeyWrapper", /* tp_name */ - sizeof(keyobject), /* tp_basicsize */ - 0, /* tp_itemsize */ - /* methods */ - (destructor)keyobject_dealloc, /* tp_dealloc */ - 0, /* tp_vectorcall_offset */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_as_async */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - (ternaryfunc)keyobject_call, /* tp_call */ - 0, /* tp_str */ - PyObject_GenericGetAttr, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT, /* tp_flags */ - 0, /* tp_doc */ - (traverseproc)keyobject_traverse, /* tp_traverse */ - (inquiry)keyobject_clear, /* tp_clear */ - keyobject_richcompare, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - 0, /* tp_methods */ - keyobject_members, /* tp_members */ - 0, /* tp_getset */ +static PyType_Slot keyobject_type_slots[] = { + {Py_tp_dealloc, keyobject_dealloc}, + {Py_tp_call, keyobject_call}, + {Py_tp_getattro, PyObject_GenericGetAttr}, + {Py_tp_traverse, keyobject_traverse}, + {Py_tp_clear, keyobject_clear}, + {Py_tp_richcompare, keyobject_richcompare}, + {Py_tp_members, keyobject_members}, + {0, 0} +}; + +static PyType_Spec keyobject_type_spec = { + .name = "functools.KeyWrapper", + .basicsize = sizeof(keyobject), + .flags = Py_TPFLAGS_DEFAULT, + .slots = keyobject_type_slots }; static PyObject * @@ -555,9 +558,11 @@ keyobject_call(keyobject *ko, PyObject *args, PyObject *kwds) if (!PyArg_ParseTupleAndKeywords(args, kwds, "O:K", kwargs, &object)) return NULL; - result = PyObject_New(keyobject, &keyobject_type); - if (!result) + + result = PyObject_New(keyobject, Py_TYPE(ko)); + if (result == NULL) { return NULL; + } Py_INCREF(ko->cmp); result->cmp = ko->cmp; Py_INCREF(object); @@ -575,7 +580,7 @@ keyobject_richcompare(PyObject *ko, PyObject *other, int op) PyObject *answer; PyObject* stack[2]; - if (!Py_IS_TYPE(other, &keyobject_type)) { + if (!Py_IS_TYPE(other, Py_TYPE(ko))) { PyErr_Format(PyExc_TypeError, "other argument must be K instance"); return NULL; } @@ -609,10 +614,13 @@ functools_cmp_to_key(PyObject *self, PyObject *args, PyObject *kwds) PyObject *cmp; static char *kwargs[] = {"mycmp", NULL}; keyobject *object; + _functools_state *state; if (!PyArg_ParseTupleAndKeywords(args, kwds, "O:cmp_to_key", kwargs, &cmp)) return NULL; - object = PyObject_New(keyobject, &keyobject_type); + + state = get_functools_state(self); + object = PyObject_New(keyobject, state->keyobject_type); if (!object) return NULL; Py_INCREF(cmp); @@ -729,10 +737,6 @@ iterable is empty."); */ - -/* this object is used delimit args and keywords in the cache keys */ -static PyObject *kwd_mark = NULL; - struct lru_list_elem; struct lru_cache_object; @@ -746,33 +750,23 @@ typedef struct lru_list_elem { static void lru_list_elem_dealloc(lru_list_elem *link) { + PyTypeObject *tp = Py_TYPE(link); Py_XDECREF(link->key); Py_XDECREF(link->result); PyObject_Free(link); + Py_DECREF(tp); } -static PyTypeObject lru_list_elem_type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - "functools._lru_list_elem", /* tp_name */ - sizeof(lru_list_elem), /* tp_basicsize */ - 0, /* tp_itemsize */ - /* methods */ - (destructor)lru_list_elem_dealloc, /* tp_dealloc */ - 0, /* tp_vectorcall_offset */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_as_async */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT, /* tp_flags */ +static PyType_Slot lru_list_elem_type_slots[] = { + {Py_tp_dealloc, lru_list_elem_dealloc}, + {0, 0} +}; + +static PyType_Spec lru_list_elem_type_spec = { + .name = "functools._lru_list_elem", + .basicsize = sizeof(lru_list_elem), + .flags = Py_TPFLAGS_DEFAULT, + .slots = lru_list_elem_type_slots }; @@ -792,10 +786,9 @@ typedef struct lru_cache_object { PyObject *weakreflist; } lru_cache_object; -static PyTypeObject lru_cache_type; - static PyObject * -lru_cache_make_key(PyObject *args, PyObject *kwds, int typed) +lru_cache_make_key(_functools_state *state, PyObject *args, + PyObject *kwds, int typed) { PyObject *key, *keyword, *value; Py_ssize_t key_size, pos, key_pos, kwds_size; @@ -834,8 +827,8 @@ lru_cache_make_key(PyObject *args, PyObject *kwds, int typed) PyTuple_SET_ITEM(key, key_pos++, item); } if (kwds_size) { - Py_INCREF(kwd_mark); - PyTuple_SET_ITEM(key, key_pos++, kwd_mark); + Py_INCREF(state->kwd_mark); + PyTuple_SET_ITEM(key, key_pos++, state->kwd_mark); for (pos = 0; PyDict_Next(kwds, &pos, &keyword, &value);) { Py_INCREF(keyword); PyTuple_SET_ITEM(key, key_pos++, keyword); @@ -879,7 +872,12 @@ infinite_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwd { PyObject *result; Py_hash_t hash; - PyObject *key = lru_cache_make_key(args, kwds, self->typed); + _functools_state *state; + state = get_functools_state_by_type(Py_TYPE(self)); + if (state == NULL) { + return NULL; + } + PyObject *key = lru_cache_make_key(state, args, kwds, self->typed); if (!key) return NULL; hash = PyObject_Hash(key); @@ -979,8 +977,13 @@ bounded_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwds lru_list_elem *link; PyObject *key, *result, *testresult; Py_hash_t hash; + _functools_state *state; - key = lru_cache_make_key(args, kwds, self->typed); + state = get_functools_state_by_type(Py_TYPE(self)); + if (state == NULL) { + return NULL; + } + key = lru_cache_make_key(state, args, kwds, self->typed); if (!key) return NULL; hash = PyObject_Hash(key); @@ -1035,7 +1038,7 @@ bounded_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwds { /* Cache is not full, so put the result in a new link */ link = (lru_list_elem *)PyObject_New(lru_list_elem, - &lru_list_elem_type); + state->lru_list_elem_type); if (link == NULL) { Py_DECREF(key); Py_DECREF(result); @@ -1229,22 +1232,31 @@ lru_cache_clear_list(lru_list_elem *link) } } +static int +lru_cache_tp_clear(lru_cache_object *self) +{ + lru_list_elem *list = lru_cache_unlink_list(self); + Py_CLEAR(self->func); + Py_CLEAR(self->cache); + Py_CLEAR(self->cache_info_type); + Py_CLEAR(self->dict); + lru_cache_clear_list(list); + return 0; +} + static void lru_cache_dealloc(lru_cache_object *obj) { - lru_list_elem *list; + PyTypeObject *tp = Py_TYPE(obj); /* bpo-31095: UnTrack is needed before calling any callbacks */ PyObject_GC_UnTrack(obj); - if (obj->weakreflist != NULL) + if (obj->weakreflist != NULL) { PyObject_ClearWeakRefs((PyObject*)obj); + } - list = lru_cache_unlink_list(obj); - Py_XDECREF(obj->cache); - Py_XDECREF(obj->func); - Py_XDECREF(obj->cache_info_type); - Py_XDECREF(obj->dict); - lru_cache_clear_list(list); - Py_TYPE(obj)->tp_free(obj); + lru_cache_tp_clear(obj); + tp->tp_free(obj); + Py_DECREF(tp); } static PyObject * @@ -1323,18 +1335,6 @@ lru_cache_tp_traverse(lru_cache_object *self, visitproc visit, void *arg) return 0; } -static int -lru_cache_tp_clear(lru_cache_object *self) -{ - lru_list_elem *list = lru_cache_unlink_list(self); - Py_CLEAR(self->func); - Py_CLEAR(self->cache); - Py_CLEAR(self->cache_info_type); - Py_CLEAR(self->dict); - lru_cache_clear_list(list); - return 0; -} - PyDoc_STRVAR(lru_cache_doc, "Create a cached callable that wraps another function.\n\ @@ -1366,51 +1366,37 @@ static PyGetSetDef lru_cache_getsetlist[] = { {NULL} }; -static PyTypeObject lru_cache_type = { - PyVarObject_HEAD_INIT(NULL, 0) - "functools._lru_cache_wrapper", /* tp_name */ - sizeof(lru_cache_object), /* tp_basicsize */ - 0, /* tp_itemsize */ - /* methods */ - (destructor)lru_cache_dealloc, /* tp_dealloc */ - 0, /* tp_vectorcall_offset */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_as_async */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - (ternaryfunc)lru_cache_call, /* tp_call */ - 0, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | - Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_METHOD_DESCRIPTOR, - /* tp_flags */ - lru_cache_doc, /* tp_doc */ - (traverseproc)lru_cache_tp_traverse,/* tp_traverse */ - (inquiry)lru_cache_tp_clear, /* tp_clear */ - 0, /* tp_richcompare */ - offsetof(lru_cache_object, weakreflist), - /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - lru_cache_methods, /* tp_methods */ - 0, /* tp_members */ - lru_cache_getsetlist, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - lru_cache_descr_get, /* tp_descr_get */ - 0, /* tp_descr_set */ - offsetof(lru_cache_object, dict), /* tp_dictoffset */ - 0, /* tp_init */ - 0, /* tp_alloc */ - lru_cache_new, /* tp_new */ +static PyMemberDef lru_cache_memberlist[] = { + {"__dictoffset__", T_PYSSIZET, + offsetof(lru_cache_object, dict), READONLY}, + {"__weaklistoffset__", T_PYSSIZET, + offsetof(lru_cache_object, weakreflist), READONLY}, + {NULL} /* Sentinel */ +}; + +static PyType_Slot lru_cache_type_slots[] = { + {Py_tp_dealloc, lru_cache_dealloc}, + {Py_tp_call, lru_cache_call}, + {Py_tp_doc, (void *)lru_cache_doc}, + {Py_tp_traverse, lru_cache_tp_traverse}, + {Py_tp_clear, lru_cache_tp_clear}, + {Py_tp_methods, lru_cache_methods}, + {Py_tp_members, lru_cache_memberlist}, + {Py_tp_getset, lru_cache_getsetlist}, + {Py_tp_descr_get, lru_cache_descr_get}, + {Py_tp_new, lru_cache_new}, + {0, 0} }; +static PyType_Spec lru_cache_type_spec = { + .name = "functools._lru_cache_wrapper", + .basicsize = sizeof(lru_cache_object), + .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | + Py_TPFLAGS_METHOD_DESCRIPTOR, + .slots = lru_cache_type_slots +}; + + /* module level code ********************************************************/ PyDoc_STRVAR(_functools_doc, @@ -1423,38 +1409,83 @@ static PyMethodDef _functools_methods[] = { {NULL, NULL} /* sentinel */ }; -static void -_functools_free(void *m) -{ - // FIXME: Do not clear kwd_mark to avoid NULL pointer dereferencing if we have - // other modules instances that could use it. Will fix when PEP-573 land - // and we could move kwd_mark to a per-module state. - // Py_CLEAR(kwd_mark); -} - static int _functools_exec(PyObject *module) { - PyTypeObject *typelist[] = { - &partial_type, - &lru_cache_type - }; - - if (!kwd_mark) { - kwd_mark = _PyObject_CallNoArg((PyObject *)&PyBaseObject_Type); - if (!kwd_mark) { - return -1; - } + _functools_state *state = get_functools_state(module); + state->kwd_mark = _PyObject_CallNoArg((PyObject *)&PyBaseObject_Type); + if (state->kwd_mark == NULL) { + return -1; } - for (size_t i = 0; i < Py_ARRAY_LENGTH(typelist); i++) { - if (PyModule_AddType(module, typelist[i]) < 0) { - return -1; - } + state->partial_type = (PyTypeObject *)PyType_FromModuleAndSpec(module, + &partial_type_spec, NULL); + if (state->partial_type == NULL) { + return -1; } + if (PyModule_AddType(module, state->partial_type) < 0) { + return -1; + } + + PyObject *lru_cache_type = PyType_FromModuleAndSpec(module, + &lru_cache_type_spec, NULL); + if (lru_cache_type == NULL) { + return -1; + } + if (PyModule_AddType(module, (PyTypeObject *)lru_cache_type) < 0) { + Py_DECREF(lru_cache_type); + return -1; + } + + state->keyobject_type = (PyTypeObject *)PyType_FromModuleAndSpec(module, + &keyobject_type_spec, NULL); + if (state->keyobject_type == NULL) { + return -1; + } + if (PyModule_AddType(module, state->keyobject_type) < 0) { + return -1; + } + + state->lru_list_elem_type = (PyTypeObject *)PyType_FromModuleAndSpec( + module, &lru_list_elem_type_spec, NULL); + if (state->lru_list_elem_type == NULL) { + return -1; + } + if (PyModule_AddType(module, state->lru_list_elem_type) < 0) { + return -1; + } + return 0; } +static int +_functools_traverse(PyObject *module, visitproc visit, void *arg) +{ + _functools_state *state = get_functools_state(module); + Py_VISIT(state->kwd_mark); + Py_VISIT(state->partial_type); + Py_VISIT(state->keyobject_type); + Py_VISIT(state->lru_list_elem_type); + return 0; +} + +static int +_functools_clear(PyObject *module) +{ + _functools_state *state = get_functools_state(module); + Py_CLEAR(state->kwd_mark); + Py_CLEAR(state->partial_type); + Py_CLEAR(state->keyobject_type); + Py_CLEAR(state->lru_list_elem_type); + return 0; +} + +static void +_functools_free(void *module) +{ + _functools_clear((PyObject *)module); +} + static struct PyModuleDef_Slot _functools_slots[] = { {Py_mod_exec, _functools_exec}, {0, NULL} @@ -1462,14 +1493,14 @@ static struct PyModuleDef_Slot _functools_slots[] = { static struct PyModuleDef _functools_module = { PyModuleDef_HEAD_INIT, - "_functools", - _functools_doc, - 0, - _functools_methods, - _functools_slots, - NULL, - NULL, - _functools_free, + .m_name = "_functools", + .m_doc = _functools_doc, + .m_size = sizeof(_functools_state), + .m_methods = _functools_methods, + .m_slots = _functools_slots, + .m_traverse = _functools_traverse, + .m_clear = _functools_clear, + .m_free = _functools_free, }; PyMODINIT_FUNC |