Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7eecffd

Browse files
committed
Issue #9935: Speed up pickling of instances of user-defined classes.
1 parent 61ec8de commit 7eecffd

4 files changed

Lines changed: 104 additions & 48 deletions

File tree

Lib/test/pickletester.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import pickle
44
import pickletools
55
import copyreg
6+
import weakref
67
from http.cookies import SimpleCookie
78

89
from test.support import TestFailed, TESTFN, run_with_locale
@@ -842,6 +843,25 @@ def test_newobj_generic(self):
842843
self.assertEqual(B(x), B(y), detail)
843844
self.assertEqual(x.__dict__, y.__dict__, detail)
844845

846+
def test_newobj_proxies(self):
847+
# NEWOBJ should use the __class__ rather than the raw type
848+
classes = myclasses[:]
849+
# Cannot create weakproxies to these classes
850+
for c in (MyInt, MyTuple):
851+
classes.remove(c)
852+
for proto in protocols:
853+
for C in classes:
854+
B = C.__base__
855+
x = C(C.sample)
856+
x.foo = 42
857+
p = weakref.proxy(x)
858+
s = self.dumps(p, proto)
859+
y = self.loads(s)
860+
self.assertEqual(type(y), type(x)) # rather than type(p)
861+
detail = (proto, C, B, x, y, type(y))
862+
self.assertEqual(B(x), B(y), detail)
863+
self.assertEqual(x.__dict__, y.__dict__, detail)
864+
845865
# Register a type with copyreg, with extension code extcode. Pickle
846866
# an object of that type. Check that the resulting pickle uses opcode
847867
# (EXT[124]) under proto 2, and not in proto 1.
@@ -1008,7 +1028,6 @@ def test_bad_getattr(self):
10081028
self.assertRaises(RuntimeError, self.dumps, x, proto)
10091029
# protocol 2 don't raise a RuntimeError.
10101030
d = self.dumps(x, 2)
1011-
self.assertRaises(RuntimeError, self.loads, d)
10121031

10131032
def test_reduce_bad_iterator(self):
10141033
# Issue4176: crash when 4th and 5th items of __reduce__()

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ Core and Builtins
4343
Library
4444
-------
4545

46+
- Issue #9935: Speed up pickling of instances of user-defined classes.
47+
4648
- Issue #5639: Add a *server_hostname* argument to ``SSLContext.wrap_socket``
4749
in order to support the TLS SNI extension. ``HTTPSConnection`` and
4850
``urlopen()`` also use this argument, so that HTTPS virtual hosts are now

Modules/_pickle.c

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2840,6 +2840,28 @@ save_pers(PicklerObject *self, PyObject *obj, PyObject *func)
28402840
return status;
28412841
}
28422842

2843+
static PyObject *
2844+
get_class(PyObject *obj)
2845+
{
2846+
PyObject *cls;
2847+
static PyObject *str_class;
2848+
2849+
if (str_class == NULL) {
2850+
str_class = PyUnicode_InternFromString("__class__");
2851+
if (str_class == NULL)
2852+
return NULL;
2853+
}
2854+
cls = PyObject_GetAttr(obj, str_class);
2855+
if (cls == NULL) {
2856+
if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
2857+
PyErr_Clear();
2858+
cls = (PyObject *) Py_TYPE(obj);
2859+
Py_INCREF(cls);
2860+
}
2861+
}
2862+
return cls;
2863+
}
2864+
28432865
/* We're saving obj, and args is the 2-thru-5 tuple returned by the
28442866
* appropriate __reduce__ method for obj.
28452867
*/
@@ -2905,27 +2927,28 @@ save_reduce(PicklerObject *self, PyObject *args, PyObject *obj)
29052927
/* Protocol 2 special case: if callable's name is __newobj__, use
29062928
NEWOBJ. */
29072929
if (use_newobj) {
2908-
static PyObject *newobj_str = NULL;
2909-
PyObject *name_str;
2930+
static PyObject *newobj_str = NULL, *name_str = NULL;
2931+
PyObject *name;
29102932

29112933
if (newobj_str == NULL) {
29122934
newobj_str = PyUnicode_InternFromString("__newobj__");
2913-
if (newobj_str == NULL)
2935+
name_str = PyUnicode_InternFromString("__name__");
2936+
if (newobj_str == NULL || name_str == NULL)
29142937
return -1;
29152938
}
29162939

2917-
name_str = PyObject_GetAttrString(callable, "__name__");
2918-
if (name_str == NULL) {
2940+
name = PyObject_GetAttr(callable, name_str);
2941+
if (name == NULL) {
29192942
if (PyErr_ExceptionMatches(PyExc_AttributeError))
29202943
PyErr_Clear();
29212944
else
29222945
return -1;
29232946
use_newobj = 0;
29242947
}
29252948
else {
2926-
use_newobj = PyUnicode_Check(name_str) &&
2927-
PyUnicode_Compare(name_str, newobj_str) == 0;
2928-
Py_DECREF(name_str);
2949+
use_newobj = PyUnicode_Check(name) &&
2950+
PyUnicode_Compare(name, newobj_str) == 0;
2951+
Py_DECREF(name);
29292952
}
29302953
}
29312954
if (use_newobj) {
@@ -2941,20 +2964,14 @@ save_reduce(PicklerObject *self, PyObject *args, PyObject *obj)
29412964
}
29422965

29432966
cls = PyTuple_GET_ITEM(argtup, 0);
2944-
if (!PyObject_HasAttrString(cls, "__new__")) {
2967+
if (!PyType_Check(cls)) {
29452968
PyErr_SetString(PicklingError, "args[0] from "
2946-
"__newobj__ args has no __new__");
2969+
"__newobj__ args is not a type");
29472970
return -1;
29482971
}
29492972

29502973
if (obj != NULL) {
2951-
obj_class = PyObject_GetAttrString(obj, "__class__");
2952-
if (obj_class == NULL) {
2953-
if (PyErr_ExceptionMatches(PyExc_AttributeError))
2954-
PyErr_Clear();
2955-
else
2956-
return -1;
2957-
}
2974+
obj_class = get_class(obj);
29582975
p = obj_class != cls; /* true iff a problem */
29592976
Py_DECREF(obj_class);
29602977
if (p) {

Objects/typeobject.c

Lines changed: 48 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3010,14 +3010,19 @@ static PyObject *
30103010
import_copyreg(void)
30113011
{
30123012
static PyObject *copyreg_str;
3013+
static PyObject *mod_copyreg = NULL;
30133014

30143015
if (!copyreg_str) {
30153016
copyreg_str = PyUnicode_InternFromString("copyreg");
30163017
if (copyreg_str == NULL)
30173018
return NULL;
30183019
}
3020+
if (!mod_copyreg) {
3021+
mod_copyreg = PyImport_Import(copyreg_str);
3022+
}
30193023

3020-
return PyImport_Import(copyreg_str);
3024+
Py_XINCREF(mod_copyreg);
3025+
return mod_copyreg;
30213026
}
30223027

30233028
static PyObject *
@@ -3026,14 +3031,16 @@ slotnames(PyObject *cls)
30263031
PyObject *clsdict;
30273032
PyObject *copyreg;
30283033
PyObject *slotnames;
3034+
static PyObject *str_slotnames;
30293035

3030-
if (!PyType_Check(cls)) {
3031-
Py_INCREF(Py_None);
3032-
return Py_None;
3036+
if (str_slotnames == NULL) {
3037+
str_slotnames = PyUnicode_InternFromString("__slotnames__");
3038+
if (str_slotnames == NULL)
3039+
return NULL;
30333040
}
30343041

30353042
clsdict = ((PyTypeObject *)cls)->tp_dict;
3036-
slotnames = PyDict_GetItemString(clsdict, "__slotnames__");
3043+
slotnames = PyDict_GetItem(clsdict, str_slotnames);
30373044
if (slotnames != NULL && PyList_Check(slotnames)) {
30383045
Py_INCREF(slotnames);
30393046
return slotnames;
@@ -3067,12 +3074,20 @@ reduce_2(PyObject *obj)
30673074
PyObject *slots = NULL, *listitems = NULL, *dictitems = NULL;
30683075
PyObject *copyreg = NULL, *newobj = NULL, *res = NULL;
30693076
Py_ssize_t i, n;
3077+
static PyObject *str_getnewargs = NULL, *str_getstate = NULL,
3078+
*str_newobj = NULL;
3079+
3080+
if (str_getnewargs == NULL) {
3081+
str_getnewargs = PyUnicode_InternFromString("__getnewargs__");
3082+
str_getstate = PyUnicode_InternFromString("__getstate__");
3083+
str_newobj = PyUnicode_InternFromString("__newobj__");
3084+
if (!str_getnewargs || !str_getstate || !str_newobj)
3085+
return NULL;
3086+
}
30703087

3071-
cls = PyObject_GetAttrString(obj, "__class__");
3072-
if (cls == NULL)
3073-
return NULL;
3088+
cls = (PyObject *) Py_TYPE(obj);
30743089

3075-
getnewargs = PyObject_GetAttrString(obj, "__getnewargs__");
3090+
getnewargs = PyObject_GetAttr(obj, str_getnewargs);
30763091
if (getnewargs != NULL) {
30773092
args = PyObject_CallObject(getnewargs, NULL);
30783093
Py_DECREF(getnewargs);
@@ -3090,25 +3105,26 @@ reduce_2(PyObject *obj)
30903105
if (args == NULL)
30913106
goto end;
30923107

3093-
getstate = PyObject_GetAttrString(obj, "__getstate__");
3108+
getstate = PyObject_GetAttr(obj, str_getstate);
30943109
if (getstate != NULL) {
30953110
state = PyObject_CallObject(getstate, NULL);
30963111
Py_DECREF(getstate);
30973112
if (state == NULL)
30983113
goto end;
30993114
}
31003115
else {
3116+
PyObject **dict;
31013117
PyErr_Clear();
3102-
state = PyObject_GetAttrString(obj, "__dict__");
3103-
if (state == NULL) {
3104-
PyErr_Clear();
3118+
dict = _PyObject_GetDictPtr(obj);
3119+
if (dict && *dict)
3120+
state = *dict;
3121+
else
31053122
state = Py_None;
3106-
Py_INCREF(state);
3107-
}
3123+
Py_INCREF(state);
31083124
names = slotnames(cls);
31093125
if (names == NULL)
31103126
goto end;
3111-
if (names != Py_None) {
3127+
if (names != Py_None && PyList_GET_SIZE(names) > 0) {
31123128
assert(PyList_Check(names));
31133129
slots = PyDict_New();
31143130
if (slots == NULL)
@@ -3167,16 +3183,16 @@ reduce_2(PyObject *obj)
31673183
copyreg = import_copyreg();
31683184
if (copyreg == NULL)
31693185
goto end;
3170-
newobj = PyObject_GetAttrString(copyreg, "__newobj__");
3186+
newobj = PyObject_GetAttr(copyreg, str_newobj);
31713187
if (newobj == NULL)
31723188
goto end;
31733189

31743190
n = PyTuple_GET_SIZE(args);
31753191
args2 = PyTuple_New(n+1);
31763192
if (args2 == NULL)
31773193
goto end;
3194+
Py_INCREF(cls);
31783195
PyTuple_SET_ITEM(args2, 0, cls);
3179-
cls = NULL;
31803196
for (i = 0; i < n; i++) {
31813197
PyObject *v = PyTuple_GET_ITEM(args, i);
31823198
Py_INCREF(v);
@@ -3186,7 +3202,6 @@ reduce_2(PyObject *obj)
31863202
res = PyTuple_Pack(5, newobj, args2, state, listitems, dictitems);
31873203

31883204
end:
3189-
Py_XDECREF(cls);
31903205
Py_XDECREF(args);
31913206
Py_XDECREF(args2);
31923207
Py_XDECREF(slots);
@@ -3246,31 +3261,34 @@ object_reduce(PyObject *self, PyObject *args)
32463261
static PyObject *
32473262
object_reduce_ex(PyObject *self, PyObject *args)
32483263
{
3264+
static PyObject *str_reduce = NULL, *objreduce;
32493265
PyObject *reduce, *res;
32503266
int proto = 0;
32513267

32523268
if (!PyArg_ParseTuple(args, "|i:__reduce_ex__", &proto))
32533269
return NULL;
32543270

3255-
reduce = PyObject_GetAttrString(self, "__reduce__");
3271+
if (str_reduce == NULL) {
3272+
str_reduce = PyUnicode_InternFromString("__reduce__");
3273+
objreduce = PyDict_GetItemString(PyBaseObject_Type.tp_dict,
3274+
"__reduce__");
3275+
if (str_reduce == NULL || objreduce == NULL)
3276+
return NULL;
3277+
}
3278+
3279+
reduce = PyObject_GetAttr(self, str_reduce);
32563280
if (reduce == NULL)
32573281
PyErr_Clear();
32583282
else {
3259-
PyObject *cls, *clsreduce, *objreduce;
3283+
PyObject *cls, *clsreduce;
32603284
int override;
3261-
cls = PyObject_GetAttrString(self, "__class__");
3262-
if (cls == NULL) {
3263-
Py_DECREF(reduce);
3264-
return NULL;
3265-
}
3266-
clsreduce = PyObject_GetAttrString(cls, "__reduce__");
3267-
Py_DECREF(cls);
3285+
3286+
cls = (PyObject *) Py_TYPE(self);
3287+
clsreduce = PyObject_GetAttr(cls, str_reduce);
32683288
if (clsreduce == NULL) {
32693289
Py_DECREF(reduce);
32703290
return NULL;
32713291
}
3272-
objreduce = PyDict_GetItemString(PyBaseObject_Type.tp_dict,
3273-
"__reduce__");
32743292
override = (clsreduce != objreduce);
32753293
Py_DECREF(clsreduce);
32763294
if (override) {

0 commit comments

Comments
 (0)