bpo-26579: Add object.__getstate__(). (GH-2821)

Copying and pickling instances of subclasses of builtin types
bytearray, set, frozenset, collections.OrderedDict, collections.deque,
weakref.WeakSet, and datetime.tzinfo now copies and pickles instance attributes
implemented as slots.
This commit is contained in:
Serhiy Storchaka 2022-04-06 20:00:14 +03:00 committed by GitHub
parent f82f9ce323
commit 884eba3c76
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 389 additions and 255 deletions

View file

@ -4960,143 +4960,175 @@ _PyType_GetSlotNames(PyTypeObject *cls)
}
static PyObject *
_PyObject_GetState(PyObject *obj, int required)
object_getstate_default(PyObject *obj, int required)
{
PyObject *state;
PyObject *getstate;
PyObject *slotnames;
if (_PyObject_LookupAttr(obj, &_Py_ID(__getstate__), &getstate) < 0) {
if (required && Py_TYPE(obj)->tp_itemsize) {
PyErr_Format(PyExc_TypeError,
"cannot pickle %.200s objects",
Py_TYPE(obj)->tp_name);
return NULL;
}
if (getstate == NULL) {
PyObject *slotnames;
if (required && Py_TYPE(obj)->tp_itemsize) {
if (_PyObject_IsInstanceDictEmpty(obj)) {
state = Py_None;
Py_INCREF(state);
}
else {
state = PyObject_GenericGetDict(obj, NULL);
if (state == NULL) {
return NULL;
}
}
slotnames = _PyType_GetSlotNames(Py_TYPE(obj));
if (slotnames == NULL) {
Py_DECREF(state);
return NULL;
}
assert(slotnames == Py_None || PyList_Check(slotnames));
if (required) {
Py_ssize_t basicsize = PyBaseObject_Type.tp_basicsize;
if (Py_TYPE(obj)->tp_dictoffset &&
(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT) == 0)
{
basicsize += sizeof(PyObject *);
}
if (Py_TYPE(obj)->tp_weaklistoffset) {
basicsize += sizeof(PyObject *);
}
if (slotnames != Py_None) {
basicsize += sizeof(PyObject *) * PyList_GET_SIZE(slotnames);
}
if (Py_TYPE(obj)->tp_basicsize > basicsize) {
Py_DECREF(slotnames);
Py_DECREF(state);
PyErr_Format(PyExc_TypeError,
"cannot pickle '%.200s' object",
Py_TYPE(obj)->tp_name);
return NULL;
}
if (_PyObject_IsInstanceDictEmpty(obj)) {
state = Py_None;
Py_INCREF(state);
}
else {
state = PyObject_GenericGetDict(obj, NULL);
if (state == NULL) {
return NULL;
}
}
}
slotnames = _PyType_GetSlotNames(Py_TYPE(obj));
if (slotnames == NULL) {
if (slotnames != Py_None && PyList_GET_SIZE(slotnames) > 0) {
PyObject *slots;
Py_ssize_t slotnames_size, i;
slots = PyDict_New();
if (slots == NULL) {
Py_DECREF(slotnames);
Py_DECREF(state);
return NULL;
}
assert(slotnames == Py_None || PyList_Check(slotnames));
if (required) {
Py_ssize_t basicsize = PyBaseObject_Type.tp_basicsize;
if (Py_TYPE(obj)->tp_dictoffset &&
(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT) == 0)
{
basicsize += sizeof(PyObject *);
slotnames_size = PyList_GET_SIZE(slotnames);
for (i = 0; i < slotnames_size; i++) {
PyObject *name, *value;
name = PyList_GET_ITEM(slotnames, i);
Py_INCREF(name);
value = PyObject_GetAttr(obj, name);
if (_PyObject_LookupAttr(obj, name, &value) < 0) {
Py_DECREF(name);
goto error;
}
if (Py_TYPE(obj)->tp_weaklistoffset) {
basicsize += sizeof(PyObject *);
if (value == NULL) {
Py_DECREF(name);
/* It is not an error if the attribute is not present. */
}
if (slotnames != Py_None) {
basicsize += sizeof(PyObject *) * PyList_GET_SIZE(slotnames);
else {
int err = PyDict_SetItem(slots, name, value);
Py_DECREF(name);
Py_DECREF(value);
if (err) {
goto error;
}
}
if (Py_TYPE(obj)->tp_basicsize > basicsize) {
/* The list is stored on the class so it may mutate while we
iterate over it */
if (slotnames_size != PyList_GET_SIZE(slotnames)) {
PyErr_Format(PyExc_RuntimeError,
"__slotsname__ changed size during iteration");
goto error;
}
/* We handle errors within the loop here. */
if (0) {
error:
Py_DECREF(slotnames);
Py_DECREF(slots);
Py_DECREF(state);
PyErr_Format(PyExc_TypeError,
"cannot pickle '%.200s' object",
Py_TYPE(obj)->tp_name);
return NULL;
}
}
if (slotnames != Py_None && PyList_GET_SIZE(slotnames) > 0) {
PyObject *slots;
Py_ssize_t slotnames_size, i;
/* If we found some slot attributes, pack them in a tuple along
the original attribute dictionary. */
if (PyDict_GET_SIZE(slots) > 0) {
PyObject *state2;
slots = PyDict_New();
if (slots == NULL) {
state2 = PyTuple_Pack(2, state, slots);
Py_DECREF(state);
if (state2 == NULL) {
Py_DECREF(slotnames);
Py_DECREF(state);
Py_DECREF(slots);
return NULL;
}
slotnames_size = PyList_GET_SIZE(slotnames);
for (i = 0; i < slotnames_size; i++) {
PyObject *name, *value;
name = PyList_GET_ITEM(slotnames, i);
Py_INCREF(name);
if (_PyObject_LookupAttr(obj, name, &value) < 0) {
goto error;
}
if (value == NULL) {
Py_DECREF(name);
/* It is not an error if the attribute is not present. */
}
else {
int err = PyDict_SetItem(slots, name, value);
Py_DECREF(name);
Py_DECREF(value);
if (err) {
goto error;
}
}
/* The list is stored on the class so it may mutate while we
iterate over it */
if (slotnames_size != PyList_GET_SIZE(slotnames)) {
PyErr_Format(PyExc_RuntimeError,
"__slotsname__ changed size during iteration");
goto error;
}
/* We handle errors within the loop here. */
if (0) {
error:
Py_DECREF(slotnames);
Py_DECREF(slots);
Py_DECREF(state);
return NULL;
}
}
/* If we found some slot attributes, pack them in a tuple along
the original attribute dictionary. */
if (PyDict_GET_SIZE(slots) > 0) {
PyObject *state2;
state2 = PyTuple_Pack(2, state, slots);
Py_DECREF(state);
if (state2 == NULL) {
Py_DECREF(slotnames);
Py_DECREF(slots);
return NULL;
}
state = state2;
}
Py_DECREF(slots);
state = state2;
}
Py_DECREF(slotnames);
}
else { /* getstate != NULL */
state = _PyObject_CallNoArgs(getstate);
Py_DECREF(getstate);
if (state == NULL)
return NULL;
Py_DECREF(slots);
}
Py_DECREF(slotnames);
return state;
}
static PyObject *
object_getstate(PyObject *obj, int required)
{
PyObject *getstate, *state;
getstate = PyObject_GetAttr(obj, &_Py_ID(__getstate__));
if (getstate == NULL) {
return NULL;
}
if (PyCFunction_Check(getstate) &&
PyCFunction_GET_SELF(getstate) == obj &&
PyCFunction_GET_FUNCTION(getstate) == object___getstate__)
{
/* If __getstate__ is not overriden pass the required argument. */
state = object_getstate_default(obj, required);
}
else {
state = _PyObject_CallNoArgs(getstate);
}
Py_DECREF(getstate);
return state;
}
PyObject *
_PyObject_GetState(PyObject *obj)
{
return object_getstate(obj, 0);
}
/*[clinic input]
object.__getstate__
Helper for pickle.
[clinic start generated code]*/
static PyObject *
object___getstate___impl(PyObject *self)
/*[clinic end generated code: output=5a2500dcb6217e9e input=692314d8fbe194ee]*/
{
return object_getstate_default(self, 0);
}
static int
_PyObject_GetNewArguments(PyObject *obj, PyObject **args, PyObject **kwargs)
{
@ -5309,8 +5341,7 @@ reduce_newobj(PyObject *obj)
return NULL;
}
state = _PyObject_GetState(obj,
!hasargs && !PyList_Check(obj) && !PyDict_Check(obj));
state = object_getstate(obj, !(hasargs || PyList_Check(obj) || PyDict_Check(obj)));
if (state == NULL) {
Py_DECREF(newobj);
Py_DECREF(newargs);
@ -5558,6 +5589,7 @@ error:
static PyMethodDef object_methods[] = {
OBJECT___REDUCE_EX___METHODDEF
OBJECT___REDUCE___METHODDEF
OBJECT___GETSTATE___METHODDEF
{"__subclasshook__", object_subclasshook, METH_CLASS | METH_VARARGS,
object_subclasshook_doc},
{"__init_subclass__", object_init_subclass, METH_CLASS | METH_NOARGS,