mirror of https://github.com/python/cpython
Issue #24164: Objects that need calling ``__new__`` with keyword arguments,
can now be pickled using pickle protocols older than protocol version 4.
This commit is contained in:
parent
4e96df3b59
commit
0d554d7ef1
|
@ -112,6 +112,15 @@ indexers. For example: ``subscript[0:10:2] == slice(0, 10, 2)``
|
||||||
(Contributed by Joe Jevnik in :issue:`24379`.)
|
(Contributed by Joe Jevnik in :issue:`24379`.)
|
||||||
|
|
||||||
|
|
||||||
|
pickle
|
||||||
|
------
|
||||||
|
|
||||||
|
Objects that need calling ``__new__`` with keyword arguments, can now be pickled
|
||||||
|
using :ref:`pickle protocols <pickle-protocols>` older than protocol version 4.
|
||||||
|
Protocol version 4 already supports this case. (Contributed by Serhiy
|
||||||
|
Storchaka in :issue:`24164`.)
|
||||||
|
|
||||||
|
|
||||||
rlcomplete
|
rlcomplete
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ from types import FunctionType
|
||||||
from copyreg import dispatch_table
|
from copyreg import dispatch_table
|
||||||
from copyreg import _extension_registry, _inverted_registry, _extension_cache
|
from copyreg import _extension_registry, _inverted_registry, _extension_cache
|
||||||
from itertools import islice
|
from itertools import islice
|
||||||
|
from functools import partial
|
||||||
import sys
|
import sys
|
||||||
from sys import maxsize
|
from sys import maxsize
|
||||||
from struct import pack, unpack
|
from struct import pack, unpack
|
||||||
|
@ -544,7 +545,7 @@ class _Pickler:
|
||||||
write = self.write
|
write = self.write
|
||||||
|
|
||||||
func_name = getattr(func, "__name__", "")
|
func_name = getattr(func, "__name__", "")
|
||||||
if self.proto >= 4 and func_name == "__newobj_ex__":
|
if self.proto >= 2 and func_name == "__newobj_ex__":
|
||||||
cls, args, kwargs = args
|
cls, args, kwargs = args
|
||||||
if not hasattr(cls, "__new__"):
|
if not hasattr(cls, "__new__"):
|
||||||
raise PicklingError("args[0] from {} args has no __new__"
|
raise PicklingError("args[0] from {} args has no __new__"
|
||||||
|
@ -552,10 +553,16 @@ class _Pickler:
|
||||||
if obj is not None and cls is not obj.__class__:
|
if obj is not None and cls is not obj.__class__:
|
||||||
raise PicklingError("args[0] from {} args has the wrong class"
|
raise PicklingError("args[0] from {} args has the wrong class"
|
||||||
.format(func_name))
|
.format(func_name))
|
||||||
save(cls)
|
if self.proto >= 4:
|
||||||
save(args)
|
save(cls)
|
||||||
save(kwargs)
|
save(args)
|
||||||
write(NEWOBJ_EX)
|
save(kwargs)
|
||||||
|
write(NEWOBJ_EX)
|
||||||
|
else:
|
||||||
|
func = partial(cls.__new__, cls, *args, **kwargs)
|
||||||
|
save(func)
|
||||||
|
save(())
|
||||||
|
write(REDUCE)
|
||||||
elif self.proto >= 2 and func_name == "__newobj__":
|
elif self.proto >= 2 and func_name == "__newobj__":
|
||||||
# A __reduce__ implementation can direct protocol 2 or newer to
|
# A __reduce__ implementation can direct protocol 2 or newer to
|
||||||
# use the more efficient NEWOBJ opcode, while still
|
# use the more efficient NEWOBJ opcode, while still
|
||||||
|
|
|
@ -1580,16 +1580,14 @@ class AbstractPickleTests(unittest.TestCase):
|
||||||
x.abc = 666
|
x.abc = 666
|
||||||
for proto in protocols:
|
for proto in protocols:
|
||||||
with self.subTest(proto=proto):
|
with self.subTest(proto=proto):
|
||||||
if 2 <= proto < 4:
|
|
||||||
self.assertRaises(ValueError, self.dumps, x, proto)
|
|
||||||
continue
|
|
||||||
s = self.dumps(x, proto)
|
s = self.dumps(x, proto)
|
||||||
if proto < 1:
|
if proto < 1:
|
||||||
self.assertIn(b'\nL64206', s) # LONG
|
self.assertIn(b'\nL64206', s) # LONG
|
||||||
elif proto < 2:
|
elif proto < 2:
|
||||||
self.assertIn(b'M\xce\xfa', s) # BININT2
|
self.assertIn(b'M\xce\xfa', s) # BININT2
|
||||||
|
elif proto < 4:
|
||||||
|
self.assertIn(b'X\x04\x00\x00\x00FACE', s) # BINUNICODE
|
||||||
else:
|
else:
|
||||||
assert proto >= 4
|
|
||||||
self.assertIn(b'\x8c\x04FACE', s) # SHORT_BINUNICODE
|
self.assertIn(b'\x8c\x04FACE', s) # SHORT_BINUNICODE
|
||||||
self.assertFalse(opcode_in_pickle(pickle.NEWOBJ, s))
|
self.assertFalse(opcode_in_pickle(pickle.NEWOBJ, s))
|
||||||
self.assertEqual(opcode_in_pickle(pickle.NEWOBJ_EX, s),
|
self.assertEqual(opcode_in_pickle(pickle.NEWOBJ_EX, s),
|
||||||
|
|
|
@ -51,6 +51,9 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #24164: Objects that need calling ``__new__`` with keyword arguments,
|
||||||
|
can now be pickled using pickle protocols older than protocol version 4.
|
||||||
|
|
||||||
- Issue #25364: zipfile now works in threads disabled builds.
|
- Issue #25364: zipfile now works in threads disabled builds.
|
||||||
|
|
||||||
- Issue #25328: smtpd's SMTPChannel now correctly raises a ValueError if both
|
- Issue #25328: smtpd's SMTPChannel now correctly raises a ValueError if both
|
||||||
|
|
|
@ -153,6 +153,9 @@ typedef struct {
|
||||||
PyObject *codecs_encode;
|
PyObject *codecs_encode;
|
||||||
/* builtins.getattr, used for saving nested names with protocol < 4 */
|
/* builtins.getattr, used for saving nested names with protocol < 4 */
|
||||||
PyObject *getattr;
|
PyObject *getattr;
|
||||||
|
/* functools.partial, used for implementing __newobj_ex__ with protocols
|
||||||
|
2 and 3 */
|
||||||
|
PyObject *partial;
|
||||||
} PickleState;
|
} PickleState;
|
||||||
|
|
||||||
/* Forward declaration of the _pickle module definition. */
|
/* Forward declaration of the _pickle module definition. */
|
||||||
|
@ -200,6 +203,7 @@ _Pickle_InitState(PickleState *st)
|
||||||
PyObject *copyreg = NULL;
|
PyObject *copyreg = NULL;
|
||||||
PyObject *compat_pickle = NULL;
|
PyObject *compat_pickle = NULL;
|
||||||
PyObject *codecs = NULL;
|
PyObject *codecs = NULL;
|
||||||
|
PyObject *functools = NULL;
|
||||||
|
|
||||||
builtins = PyEval_GetBuiltins();
|
builtins = PyEval_GetBuiltins();
|
||||||
if (builtins == NULL)
|
if (builtins == NULL)
|
||||||
|
@ -314,12 +318,21 @@ _Pickle_InitState(PickleState *st)
|
||||||
}
|
}
|
||||||
Py_CLEAR(codecs);
|
Py_CLEAR(codecs);
|
||||||
|
|
||||||
|
functools = PyImport_ImportModule("functools");
|
||||||
|
if (!functools)
|
||||||
|
goto error;
|
||||||
|
st->partial = PyObject_GetAttrString(functools, "partial");
|
||||||
|
if (!st->partial)
|
||||||
|
goto error;
|
||||||
|
Py_CLEAR(functools);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
Py_CLEAR(copyreg);
|
Py_CLEAR(copyreg);
|
||||||
Py_CLEAR(compat_pickle);
|
Py_CLEAR(compat_pickle);
|
||||||
Py_CLEAR(codecs);
|
Py_CLEAR(codecs);
|
||||||
|
Py_CLEAR(functools);
|
||||||
_Pickle_ClearState(st);
|
_Pickle_ClearState(st);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -3533,11 +3546,9 @@ save_reduce(PicklerObject *self, PyObject *args, PyObject *obj)
|
||||||
PyErr_Clear();
|
PyErr_Clear();
|
||||||
}
|
}
|
||||||
else if (PyUnicode_Check(name)) {
|
else if (PyUnicode_Check(name)) {
|
||||||
if (self->proto >= 4) {
|
_Py_IDENTIFIER(__newobj_ex__);
|
||||||
_Py_IDENTIFIER(__newobj_ex__);
|
use_newobj_ex = PyUnicode_Compare(
|
||||||
use_newobj_ex = PyUnicode_Compare(
|
name, _PyUnicode_FromId(&PyId___newobj_ex__)) == 0;
|
||||||
name, _PyUnicode_FromId(&PyId___newobj_ex__)) == 0;
|
|
||||||
}
|
|
||||||
if (!use_newobj_ex) {
|
if (!use_newobj_ex) {
|
||||||
_Py_IDENTIFIER(__newobj__);
|
_Py_IDENTIFIER(__newobj__);
|
||||||
use_newobj = PyUnicode_Compare(
|
use_newobj = PyUnicode_Compare(
|
||||||
|
@ -3581,11 +3592,58 @@ save_reduce(PicklerObject *self, PyObject *args, PyObject *obj)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (save(self, cls, 0) < 0 ||
|
if (self->proto >= 4) {
|
||||||
save(self, args, 0) < 0 ||
|
if (save(self, cls, 0) < 0 ||
|
||||||
save(self, kwargs, 0) < 0 ||
|
save(self, args, 0) < 0 ||
|
||||||
_Pickler_Write(self, &newobj_ex_op, 1) < 0) {
|
save(self, kwargs, 0) < 0 ||
|
||||||
return -1;
|
_Pickler_Write(self, &newobj_ex_op, 1) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
PyObject *newargs;
|
||||||
|
PyObject *cls_new;
|
||||||
|
Py_ssize_t i;
|
||||||
|
_Py_IDENTIFIER(__new__);
|
||||||
|
|
||||||
|
newargs = PyTuple_New(Py_SIZE(args) + 2);
|
||||||
|
if (newargs == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
cls_new = _PyObject_GetAttrId(cls, &PyId___new__);
|
||||||
|
if (cls_new == NULL) {
|
||||||
|
Py_DECREF(newargs);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
PyTuple_SET_ITEM(newargs, 0, cls_new);
|
||||||
|
Py_INCREF(cls);
|
||||||
|
PyTuple_SET_ITEM(newargs, 1, cls);
|
||||||
|
for (i = 0; i < Py_SIZE(args); i++) {
|
||||||
|
PyObject *item = PyTuple_GET_ITEM(args, i);
|
||||||
|
Py_INCREF(item);
|
||||||
|
PyTuple_SET_ITEM(newargs, i + 2, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
callable = PyObject_Call(st->partial, newargs, kwargs);
|
||||||
|
Py_DECREF(newargs);
|
||||||
|
if (callable == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
newargs = PyTuple_New(0);
|
||||||
|
if (newargs == NULL) {
|
||||||
|
Py_DECREF(callable);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (save(self, callable, 0) < 0 ||
|
||||||
|
save(self, newargs, 0) < 0 ||
|
||||||
|
_Pickler_Write(self, &reduce_op, 1) < 0) {
|
||||||
|
Py_DECREF(newargs);
|
||||||
|
Py_DECREF(callable);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
Py_DECREF(newargs);
|
||||||
|
Py_DECREF(callable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (use_newobj) {
|
else if (use_newobj) {
|
||||||
|
|
|
@ -4101,7 +4101,7 @@ _PyObject_GetItemsIter(PyObject *obj, PyObject **listitems,
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
reduce_newobj(PyObject *obj, int proto)
|
reduce_newobj(PyObject *obj)
|
||||||
{
|
{
|
||||||
PyObject *args = NULL, *kwargs = NULL;
|
PyObject *args = NULL, *kwargs = NULL;
|
||||||
PyObject *copyreg;
|
PyObject *copyreg;
|
||||||
|
@ -4153,7 +4153,7 @@ reduce_newobj(PyObject *obj, int proto)
|
||||||
}
|
}
|
||||||
Py_DECREF(args);
|
Py_DECREF(args);
|
||||||
}
|
}
|
||||||
else if (proto >= 4) {
|
else {
|
||||||
_Py_IDENTIFIER(__newobj_ex__);
|
_Py_IDENTIFIER(__newobj_ex__);
|
||||||
|
|
||||||
newobj = _PyObject_GetAttrId(copyreg, &PyId___newobj_ex__);
|
newobj = _PyObject_GetAttrId(copyreg, &PyId___newobj_ex__);
|
||||||
|
@ -4171,16 +4171,6 @@ reduce_newobj(PyObject *obj, int proto)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
PyErr_SetString(PyExc_ValueError,
|
|
||||||
"must use protocol 4 or greater to copy this "
|
|
||||||
"object; since __getnewargs_ex__ returned "
|
|
||||||
"keyword arguments.");
|
|
||||||
Py_DECREF(args);
|
|
||||||
Py_DECREF(kwargs);
|
|
||||||
Py_DECREF(copyreg);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
state = _PyObject_GetState(obj);
|
state = _PyObject_GetState(obj);
|
||||||
if (state == NULL) {
|
if (state == NULL) {
|
||||||
|
@ -4225,7 +4215,7 @@ _common_reduce(PyObject *self, int proto)
|
||||||
PyObject *copyreg, *res;
|
PyObject *copyreg, *res;
|
||||||
|
|
||||||
if (proto >= 2)
|
if (proto >= 2)
|
||||||
return reduce_newobj(self, proto);
|
return reduce_newobj(self);
|
||||||
|
|
||||||
copyreg = import_copyreg();
|
copyreg = import_copyreg();
|
||||||
if (!copyreg)
|
if (!copyreg)
|
||||||
|
|
Loading…
Reference in New Issue