diff -r 89821243621b Doc/library/pickle.rst --- a/Doc/library/pickle.rst Thu Jul 14 07:45:24 2016 +0300 +++ b/Doc/library/pickle.rst Thu Jul 14 14:35:06 2016 +0300 @@ -422,9 +422,8 @@ The following types can be pickled: * classes that are defined at the top level of a module -* instances of such classes whose :attr:`~object.__dict__` or the result of - calling :meth:`__getstate__` is picklable (see section :ref:`pickle-inst` for - details). +* instances of such classes whose the result of calling :meth:`__getstate__` + is picklable (see section :ref:`pickle-inst` for details). Attempts to pickle unpicklable objects will raise the :exc:`PicklingError` exception; when this happens, an unspecified number of bytes may have already @@ -524,11 +523,30 @@ methods: .. method:: object.__getstate__() - Classes can further influence how their instances are pickled; if the class - defines the method :meth:`__getstate__`, it is called and the returned object - is pickled as the contents for the instance, instead of the contents of the - instance's dictionary. If the :meth:`__getstate__` method is absent, the - instance's :attr:`~object.__dict__` is pickled as usual. + Classes can further influence how their instances are pickled by overriding + the method :meth:`__getstate__`. It is called and the returned object + is pickled as the contents for the instance, instead of a default state. + There are several cases: + + * For a class that has no instance :attr:`~object.__dict__` and no + :attr:`~object.__slots__`, the default state is ``None``. + + * For a class that has an instance :attr:`~object.__dict__` and no + :attr:`~object.__slots__`, the default state is ``self.__dict__``. + + * For a class that has an instance :attr:`~object.__dict__` and + :attr:`~object.__slots__`, the default state is a tuple consisting of two + dictionaries: ``self.__dict__``, and a dictionary mapping slot + names to slot values. Only slots that have a value are + included in the latter. + + * For a class that has :attr:`~object.__slots__` and no instance + :attr:`~object.__dict__`, the default state is a tuple whose first item + is ``None`` and whose second item is a dictionary mapping slot names + to slot values described in the previous bullet. + + .. versionchanged:: 3.6 + Provided the default implementation of :meth:`object.__getstate__`. .. method:: object.__setstate__(state) diff -r 89821243621b Include/object.h --- a/Include/object.h Thu Jul 14 07:45:24 2016 +0300 +++ b/Include/object.h Thu Jul 14 14:35:06 2016 +0300 @@ -578,6 +578,11 @@ PyAPI_FUNC(PyObject *) */ PyAPI_FUNC(PyObject *) PyObject_Dir(PyObject *); +/* Pickle support. */ +#ifndef Py_LIMITED_API +PyAPI_FUNC(PyObject *) _PyObject_GetState(PyObject *); +#endif + /* Helpers for printing recursive container types */ PyAPI_FUNC(int) Py_ReprEnter(PyObject *); diff -r 89821243621b Lib/_weakrefset.py --- a/Lib/_weakrefset.py Thu Jul 14 07:45:24 2016 +0300 +++ b/Lib/_weakrefset.py Thu Jul 14 14:35:06 2016 +0300 @@ -75,8 +75,7 @@ class WeakSet: return wr in self.data def __reduce__(self): - return (self.__class__, (list(self),), - getattr(self, '__dict__', None)) + return self.__class__, (list(self),), self.__getstate__() def add(self, item): if self._pending_removals: diff -r 89821243621b Lib/collections/__init__.py --- a/Lib/collections/__init__.py Thu Jul 14 07:45:24 2016 +0300 +++ b/Lib/collections/__init__.py Thu Jul 14 14:35:06 2016 +0300 @@ -260,10 +260,22 @@ class OrderedDict(dict): def __reduce__(self): 'Return state information for pickling' - inst_dict = vars(self).copy() - for k in vars(OrderedDict()): - inst_dict.pop(k, None) - return self.__class__, (), inst_dict or None, None, iter(self.items()) + state = self.__getstate__() + if state: + if isinstance(state, tuple): + state, slots = state + else: + slots = {} + state = state.copy() + slots = slots.copy() + for k in vars(OrderedDict()): + state.pop(k, None) + slots.pop(k, None) + if slots: + state = state, slots + else: + state = state or None + return self.__class__, (), state, None, iter(self.items()) def copy(self): 'od.copy() -> a shallow copy of od' diff -r 89821243621b Lib/datetime.py --- a/Lib/datetime.py Thu Jul 14 07:45:24 2016 +0300 +++ b/Lib/datetime.py Thu Jul 14 14:35:06 2016 +0300 @@ -1005,15 +1005,7 @@ class tzinfo: args = getinitargs() else: args = () - getstate = getattr(self, "__getstate__", None) - if getstate: - state = getstate() - else: - state = getattr(self, "__dict__", None) or None - if state is None: - return (self.__class__, args) - else: - return (self.__class__, args, state) + return (self.__class__, args, self.__getstate__()) _tzinfo_class = tzinfo diff -r 89821243621b Lib/email/headerregistry.py --- a/Lib/email/headerregistry.py Thu Jul 14 07:45:24 2016 +0300 +++ b/Lib/email/headerregistry.py Thu Jul 14 14:35:06 2016 +0300 @@ -223,7 +223,7 @@ class BaseHeader(str): self.__class__.__bases__, str(self), ), - self.__dict__) + self.__getstate__()) @classmethod def _reconstruct(cls, value): diff -r 89821243621b Lib/test/datetimetester.py --- a/Lib/test/datetimetester.py Thu Jul 14 07:45:24 2016 +0300 +++ b/Lib/test/datetimetester.py Thu Jul 14 14:35:06 2016 +0300 @@ -117,8 +117,8 @@ class PicklableFixedOffset(FixedOffset): def __init__(self, offset=None, name=None, dstoffset=None): FixedOffset.__init__(self, offset, name, dstoffset) - def __getstate__(self): - return self.__dict__ +class PicklableFixedOffsetWithSlots(PicklableFixedOffset): + __slots__ = '_FixedOffset__offset', '_FixedOffset__name', 'spam' class _TZInfo(tzinfo): def utcoffset(self, datetime_module): @@ -180,6 +180,7 @@ class TestTZInfo(unittest.TestCase): offset = timedelta(minutes=-300) for otype, args in [ (PicklableFixedOffset, (offset, 'cookie')), + (PicklableFixedOffsetWithSlots, (offset, 'cookie')), (timezone, (offset,)), (timezone, (offset, "EST"))]: orig = otype(*args) @@ -195,6 +196,7 @@ class TestTZInfo(unittest.TestCase): self.assertIs(type(derived), otype) self.assertEqual(derived.utcoffset(None), offset) self.assertEqual(derived.tzname(None), oname) + self.assertFalse(hasattr(derived, 'spam')) def test_issue23600(self): DSTDIFF = DSTOFFSET = timedelta(hours=1) diff -r 89821243621b Lib/test/test_bytes.py --- a/Lib/test/test_bytes.py Thu Jul 14 07:45:24 2016 +0300 +++ b/Lib/test/test_bytes.py Thu Jul 14 14:35:06 2016 +0300 @@ -1577,28 +1577,30 @@ class SubclassTest: def test_pickle(self): a = self.type2test(b"abcd") a.x = 10 - a.y = self.type2test(b"efgh") + a.z = self.type2test(b"efgh") for proto in range(pickle.HIGHEST_PROTOCOL + 1): b = pickle.loads(pickle.dumps(a, proto)) self.assertNotEqual(id(a), id(b)) self.assertEqual(a, b) self.assertEqual(a.x, b.x) - self.assertEqual(a.y, b.y) + self.assertEqual(a.z, b.z) self.assertEqual(type(a), type(b)) - self.assertEqual(type(a.y), type(b.y)) + self.assertEqual(type(a.z), type(b.z)) + self.assertFalse(hasattr(b, 'y')) def test_copy(self): a = self.type2test(b"abcd") a.x = 10 - a.y = self.type2test(b"efgh") + a.z = self.type2test(b"efgh") for copy_method in (copy.copy, copy.deepcopy): b = copy_method(a) self.assertNotEqual(id(a), id(b)) self.assertEqual(a, b) self.assertEqual(a.x, b.x) - self.assertEqual(a.y, b.y) + self.assertEqual(a.z, b.z) self.assertEqual(type(a), type(b)) - self.assertEqual(type(a.y), type(b.y)) + self.assertEqual(type(a.z), type(b.z)) + self.assertFalse(hasattr(b, 'y')) def test_fromhex(self): b = self.type2test.fromhex('1a2B30') @@ -1631,6 +1633,9 @@ class SubclassTest: class ByteArraySubclass(bytearray): pass +class ByteArraySubclassWithSlots(bytearray): + __slots__ = ('x', 'z', '__dict__') + class BytesSubclass(bytes): pass @@ -1651,6 +1656,9 @@ class ByteArraySubclassTest(SubclassTest x = subclass(newarg=4, source=b"abcd") self.assertEqual(x, b"abcd") +class ByteArraySubclassWithSlotsTest(SubclassTest, unittest.TestCase): + basetype = bytearray + type2test = ByteArraySubclassWithSlots class BytesSubclassTest(SubclassTest, unittest.TestCase): basetype = bytes diff -r 89821243621b Lib/test/test_deque.py --- a/Lib/test/test_deque.py Thu Jul 14 07:45:24 2016 +0300 +++ b/Lib/test/test_deque.py Thu Jul 14 14:35:06 2016 +0300 @@ -792,6 +792,9 @@ class TestVariousIteratorArgs(unittest.T class Deque(deque): pass +class DequeWithSlots(deque): + __slots__ = ('x', 'y', '__dict__') + class DequeWithBadIter(deque): def __iter__(self): raise TypeError @@ -821,40 +824,28 @@ class TestSubclass(unittest.TestCase): self.assertEqual(len(d), 0) def test_copy_pickle(self): + for cls in Deque, DequeWithSlots: + for d in cls('abc'), cls('abcde', maxlen=4): + d.x = ['x'] + d.z = ['z'] - d = Deque('abc') + e = d.__copy__() + self.assertEqual(type(d), type(e)) + self.assertEqual(list(d), list(e)) - e = d.__copy__() - self.assertEqual(type(d), type(e)) - self.assertEqual(list(d), list(e)) + e = cls(d) + self.assertEqual(type(d), type(e)) + self.assertEqual(list(d), list(e)) - e = Deque(d) - self.assertEqual(type(d), type(e)) - self.assertEqual(list(d), list(e)) - - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - s = pickle.dumps(d, proto) - e = pickle.loads(s) - self.assertNotEqual(id(d), id(e)) - self.assertEqual(type(d), type(e)) - self.assertEqual(list(d), list(e)) - - d = Deque('abcde', maxlen=4) - - e = d.__copy__() - self.assertEqual(type(d), type(e)) - self.assertEqual(list(d), list(e)) - - e = Deque(d) - self.assertEqual(type(d), type(e)) - self.assertEqual(list(d), list(e)) - - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - s = pickle.dumps(d, proto) - e = pickle.loads(s) - self.assertNotEqual(id(d), id(e)) - self.assertEqual(type(d), type(e)) - self.assertEqual(list(d), list(e)) + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + s = pickle.dumps(d, proto) + e = pickle.loads(s) + self.assertNotEqual(id(d), id(e)) + self.assertEqual(type(d), type(e)) + self.assertEqual(list(d), list(e)) + self.assertEqual(e.x, d.x) + self.assertEqual(e.z, d.z) + self.assertFalse(hasattr(e, 'y')) def test_pickle_recursive(self): for proto in range(pickle.HIGHEST_PROTOCOL + 1): diff -r 89821243621b Lib/test/test_ordered_dict.py --- a/Lib/test/test_ordered_dict.py Thu Jul 14 07:45:24 2016 +0300 +++ b/Lib/test/test_ordered_dict.py Thu Jul 14 14:35:06 2016 +0300 @@ -262,6 +262,8 @@ class OrderedDictTests: # and have a repr/eval round-trip pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)] od = OrderedDict(pairs) + od.x = ['x'] + od.z = ['z'] def check(dup): msg = "\ncopy: %s\nod: %s" % (dup, od) self.assertIsNot(dup, od, msg) @@ -270,13 +272,27 @@ class OrderedDictTests: self.assertEqual(len(dup), len(od)) self.assertEqual(type(dup), type(od)) check(od.copy()) - check(copy.copy(od)) - check(copy.deepcopy(od)) + dup = copy.copy(od) + check(dup) + self.assertIs(dup.x, od.x) + self.assertIs(dup.z, od.z) + self.assertFalse(hasattr(dup, 'y')) + dup = copy.deepcopy(od) + check(dup) + self.assertEqual(dup.x, od.x) + self.assertIsNot(dup.x, od.x) + self.assertEqual(dup.z, od.z) + self.assertIsNot(dup.z, od.z) + self.assertFalse(hasattr(dup, 'y')) # pickle directly pulls the module, so we have to fake it with replaced_module('collections', self.module): for proto in range(pickle.HIGHEST_PROTOCOL + 1): with self.subTest(proto=proto): - check(pickle.loads(pickle.dumps(od, proto))) + dup = pickle.loads(pickle.dumps(od, proto)) + check(dup) + self.assertEqual(dup.x, od.x) + self.assertEqual(dup.z, od.z) + self.assertFalse(hasattr(dup, 'y')) check(eval(repr(od))) update_test = OrderedDict() update_test.update(od) @@ -690,6 +706,23 @@ class CPythonOrderedDictSubclassTests(CP pass +class PurePythonOrderedDictWithSlotsCopyingTests(unittest.TestCase): + + module = py_coll + class OrderedDict(py_coll.OrderedDict): + __slots__ = ('x', 'y') + test_copying = OrderedDictTests.test_copying + + +@unittest.skipUnless(c_coll, 'requires the C version of the collections module') +class CPythonOrderedDictWithSlotsCopyingTests(unittest.TestCase): + + module = c_coll + class OrderedDict(c_coll.OrderedDict): + __slots__ = ('x', 'y') + test_copying = OrderedDictTests.test_copying + + class PurePythonGeneralMappingTests(mapping_tests.BasicTestMappingProtocol): @classmethod diff -r 89821243621b Lib/test/test_set.py --- a/Lib/test/test_set.py Thu Jul 14 07:45:24 2016 +0300 +++ b/Lib/test/test_set.py Thu Jul 14 14:35:06 2016 +0300 @@ -227,14 +227,17 @@ class TestJointOps: def test_pickling(self): for i in range(pickle.HIGHEST_PROTOCOL + 1): + if type(self.s) not in (set, frozenset): + self.s.x = ['x'] + self.s.z = ['z'] p = pickle.dumps(self.s, i) dup = pickle.loads(p) self.assertEqual(self.s, dup, "%s != %s" % (self.s, dup)) if type(self.s) not in (set, frozenset): - self.s.x = 10 - p = pickle.dumps(self.s, i) - dup = pickle.loads(p) self.assertEqual(self.s.x, dup.x) + self.assertEqual(self.s.z, dup.z) + self.assertFalse(hasattr(self.s, 'y')) + del self.s.x, self.s.z def test_iterator_pickling(self): for proto in range(pickle.HIGHEST_PROTOCOL + 1): @@ -769,6 +772,21 @@ class TestFrozenSetSubclass(TestFrozenSe # All empty frozenset subclass instances should have different ids self.assertEqual(len(set(map(id, efs))), len(efs)) + +class SetSubclassWithSlots(set): + __slots__ = ('x', 'y', '__dict__') + +class TestSetSubclassWithSlots(unittest.TestCase): + thetype = SetSubclassWithSlots + setUp = TestJointOps.setUp + test_pickling = TestJointOps.test_pickling + +class FrozenSetSubclassWithSlots(frozenset): + __slots__ = ('x', 'y', '__dict__') + +class TestFrozenSetSubclassWithSlots(TestSetSubclassWithSlots): + thetype = FrozenSetSubclassWithSlots + # Tests taken from test_sets.py ============================================= empty_set = set() diff -r 89821243621b Lib/test/test_weakset.py --- a/Lib/test/test_weakset.py Thu Jul 14 07:45:24 2016 +0300 +++ b/Lib/test/test_weakset.py Thu Jul 14 14:35:06 2016 +0300 @@ -1,5 +1,6 @@ import unittest from weakref import WeakSet +import copy import string from collections import UserString as ustr import gc @@ -13,6 +14,12 @@ class RefCycle: def __init__(self): self.cycle = self +class WeakSetSubclass(WeakSet): + pass + +class WeakSetWithSlots(WeakSet): + __slots__ = ('x', 'y') + class TestWeakSet(unittest.TestCase): @@ -434,6 +441,30 @@ class TestWeakSet(unittest.TestCase): self.assertGreaterEqual(n2, 0) self.assertLessEqual(n2, n1) + def test_copying(self): + for cls in WeakSet, WeakSetWithSlots: + s = cls(self.items) + s.x = ['x'] + s.z = ['z'] + + dup = copy.copy(s) + self.assertIsInstance(dup, cls) + self.assertEqual(dup, s) + self.assertIsNot(dup, s) + self.assertIs(dup.x, s.x) + self.assertIs(dup.z, s.z) + self.assertFalse(hasattr(dup, 'y')) + + dup = copy.deepcopy(s) + self.assertIsInstance(dup, cls) + self.assertEqual(dup, s) + self.assertIsNot(dup, s) + self.assertEqual(dup.x, s.x) + self.assertIsNot(dup.x, s.x) + self.assertEqual(dup.z, s.z) + self.assertIsNot(dup.z, s.z) + self.assertFalse(hasattr(dup, 'y')) + if __name__ == "__main__": unittest.main() diff -r 89821243621b Lib/test/test_xml_etree.py --- a/Lib/test/test_xml_etree.py Thu Jul 14 07:45:24 2016 +0300 +++ b/Lib/test/test_xml_etree.py Thu Jul 14 14:35:06 2016 +0300 @@ -1804,8 +1804,7 @@ class BasicElementTest(ElementTestCase, 4 """ e1 = dumper.fromstring(XMLTEXT) - if hasattr(e1, '__getstate__'): - self.assertEqual(e1.__getstate__()['tag'], 'group') + self.assertEqual(e1.__getstate__()['tag'], 'group') e2 = self.pickleRoundTrip(e1, 'xml.etree.ElementTree', dumper, loader, proto) self.assertEqual(e2.tag, 'group') diff -r 89821243621b Modules/_collectionsmodule.c --- a/Modules/_collectionsmodule.c Thu Jul 14 07:45:24 2016 +0300 +++ b/Modules/_collectionsmodule.c Thu Jul 14 14:35:06 2016 +0300 @@ -1296,30 +1296,24 @@ deque_traverse(dequeobject *deque, visit static PyObject * deque_reduce(dequeobject *deque) { - PyObject *dict, *it; - _Py_IDENTIFIER(__dict__); + PyObject *state, *it; - dict = _PyObject_GetAttrId((PyObject *)deque, &PyId___dict__); - if (dict == NULL) { - if (!PyErr_ExceptionMatches(PyExc_AttributeError)) { - return NULL; - } - PyErr_Clear(); - dict = Py_None; - Py_INCREF(dict); + state = _PyObject_GetState((PyObject *)deque); + if (state == NULL) { + return NULL; } it = PyObject_GetIter((PyObject *)deque); if (it == NULL) { - Py_DECREF(dict); + Py_DECREF(state); return NULL; } if (deque->maxlen < 0) { - return Py_BuildValue("O()NN", Py_TYPE(deque), dict, it); + return Py_BuildValue("O()NN", Py_TYPE(deque), state, it); } else { - return Py_BuildValue("O(()n)NN", Py_TYPE(deque), deque->maxlen, dict, it); + return Py_BuildValue("O(()n)NN", Py_TYPE(deque), deque->maxlen, state, it); } } diff -r 89821243621b Modules/_datetimemodule.c --- a/Modules/_datetimemodule.c Thu Jul 14 07:45:24 2016 +0300 +++ b/Modules/_datetimemodule.c Thu Jul 14 14:35:06 2016 +0300 @@ -3077,9 +3077,8 @@ static PyObject * tzinfo_reduce(PyObject *self) { PyObject *args, *state, *tmp; - PyObject *getinitargs, *getstate; + PyObject *getinitargs; _Py_IDENTIFIER(__getinitargs__); - _Py_IDENTIFIER(__getstate__); tmp = PyTuple_New(0); if (tmp == NULL) @@ -3100,34 +3099,13 @@ tzinfo_reduce(PyObject *self) Py_INCREF(args); } - getstate = _PyObject_GetAttrId(self, &PyId___getstate__); - if (getstate != NULL) { - state = PyObject_CallObject(getstate, tmp); - Py_DECREF(getstate); - if (state == NULL) { - Py_DECREF(args); - Py_DECREF(tmp); - return NULL; - } + state = _PyObject_GetState(self); + if (state == NULL) { + Py_DECREF(args); + return NULL; } - else { - PyObject **dictptr; - PyErr_Clear(); - state = Py_None; - dictptr = _PyObject_GetDictPtr(self); - if (dictptr && *dictptr && PyDict_Size(*dictptr)) - state = *dictptr; - Py_INCREF(state); - } - - Py_DECREF(tmp); - - if (state == Py_None) { - Py_DECREF(state); - return Py_BuildValue("(ON)", Py_TYPE(self), args); - } - else - return Py_BuildValue("(ONN)", Py_TYPE(self), args, state); + + return Py_BuildValue("(ONN)", Py_TYPE(self), args, state); } static PyMethodDef tzinfo_methods[] = { diff -r 89821243621b Objects/bytearrayobject.c --- a/Objects/bytearrayobject.c Thu Jul 14 07:45:24 2016 +0300 +++ b/Objects/bytearrayobject.c Thu Jul 14 14:35:06 2016 +0300 @@ -2003,35 +2003,26 @@ bytearray_hex(PyBytesObject *self) static PyObject * _common_reduce(PyByteArrayObject *self, int proto) { - PyObject *dict; - _Py_IDENTIFIER(__dict__); - char *buf; + PyObject *state; + const char *buf; - dict = _PyObject_GetAttrId((PyObject *)self, &PyId___dict__); - if (dict == NULL) { - PyErr_Clear(); - dict = Py_None; - Py_INCREF(dict); + state = _PyObject_GetState((PyObject *)self); + if (state == NULL) { + return NULL; } + if (!Py_SIZE(self)) { + return Py_BuildValue("(O()N)", Py_TYPE(self), state); + } buf = PyByteArray_AS_STRING(self); if (proto < 3) { /* use str based reduction for backwards compatibility with Python 2.x */ - PyObject *latin1; - if (Py_SIZE(self)) - latin1 = PyUnicode_DecodeLatin1(buf, Py_SIZE(self), NULL); - else - latin1 = PyUnicode_FromString(""); - return Py_BuildValue("(O(Ns)N)", Py_TYPE(self), latin1, "latin-1", dict); + PyObject *latin1 = PyUnicode_DecodeLatin1(buf, Py_SIZE(self), NULL); + return Py_BuildValue("(O(Ns)N)", Py_TYPE(self), latin1, "latin-1", state); } else { /* use more efficient byte based reduction */ - if (Py_SIZE(self)) { - return Py_BuildValue("(O(y#)N)", Py_TYPE(self), buf, Py_SIZE(self), dict); - } - else { - return Py_BuildValue("(O()N)", Py_TYPE(self), dict); - } + return Py_BuildValue("(O(y#)N)", Py_TYPE(self), buf, Py_SIZE(self), state); } } diff -r 89821243621b Objects/odictobject.c --- a/Objects/odictobject.c Thu Jul 14 07:45:24 2016 +0300 +++ b/Objects/odictobject.c Thu Jul 14 14:35:06 2016 +0300 @@ -953,25 +953,14 @@ PyDoc_STRVAR(odict_reduce__doc__, "Retur static PyObject * odict_reduce(register PyODictObject *od) { - _Py_IDENTIFIER(__dict__); _Py_IDENTIFIER(items); - PyObject *dict = NULL, *result = NULL; + PyObject *state, *result = NULL; PyObject *items_iter, *items, *args = NULL; /* capture any instance state */ - dict = _PyObject_GetAttrId((PyObject *)od, &PyId___dict__); - if (dict == NULL) + state = _PyObject_GetState((PyObject *)od); + if (state == NULL) goto Done; - else { - /* od.__dict__ isn't necessarily a dict... */ - Py_ssize_t dict_len = PyObject_Length(dict); - if (dict_len == -1) - goto Done; - if (!dict_len) { - /* nothing to pickle in od.__dict__ */ - Py_CLEAR(dict); - } - } /* build the result */ args = PyTuple_New(0); @@ -987,11 +976,11 @@ odict_reduce(register PyODictObject *od) if (items_iter == NULL) goto Done; - result = PyTuple_Pack(5, Py_TYPE(od), args, dict ? dict : Py_None, Py_None, items_iter); + result = PyTuple_Pack(5, Py_TYPE(od), args, state, Py_None, items_iter); Py_DECREF(items_iter); Done: - Py_XDECREF(dict); + Py_DECREF(state); Py_XDECREF(args); return result; diff -r 89821243621b Objects/setobject.c --- a/Objects/setobject.c Thu Jul 14 07:45:24 2016 +0300 +++ b/Objects/setobject.c Thu Jul 14 14:35:06 2016 +0300 @@ -1957,8 +1957,7 @@ If the element is not a member, do nothi static PyObject * set_reduce(PySetObject *so) { - PyObject *keys=NULL, *args=NULL, *result=NULL, *dict=NULL; - _Py_IDENTIFIER(__dict__); + PyObject *keys=NULL, *args=NULL, *result=NULL, *state=NULL; keys = PySequence_List((PyObject *)so); if (keys == NULL) @@ -1966,17 +1965,14 @@ set_reduce(PySetObject *so) args = PyTuple_Pack(1, keys); if (args == NULL) goto done; - dict = _PyObject_GetAttrId((PyObject *)so, &PyId___dict__); - if (dict == NULL) { - PyErr_Clear(); - dict = Py_None; - Py_INCREF(dict); - } - result = PyTuple_Pack(3, Py_TYPE(so), args, dict); + state = _PyObject_GetState((PyObject *)so); + if (state == NULL) + goto done; + result = PyTuple_Pack(3, Py_TYPE(so), args, state); done: Py_XDECREF(args); Py_XDECREF(keys); - Py_XDECREF(dict); + Py_XDECREF(state); return result; } diff -r 89821243621b Objects/typeobject.c --- a/Objects/typeobject.c Thu Jul 14 07:45:24 2016 +0300 +++ b/Objects/typeobject.c Thu Jul 14 14:35:06 2016 +0300 @@ -3827,151 +3827,148 @@ Py_LOCAL(PyObject *) } Py_LOCAL(PyObject *) -_PyObject_GetState(PyObject *obj, int required) +default_getstate(PyObject *obj, int required) { PyObject *state; - PyObject *getstate; - _Py_IDENTIFIER(__getstate__); - - getstate = _PyObject_GetAttrId(obj, &PyId___getstate__); - if (getstate == NULL) { - PyObject *slotnames; - - if (!PyErr_ExceptionMatches(PyExc_AttributeError)) { - return NULL; + PyObject *slotnames; + + if (required && obj->ob_type->tp_itemsize) { + PyErr_Format(PyExc_TypeError, + "can't pickle %.200s objects", + Py_TYPE(obj)->tp_name); + return NULL; + } + + { + PyObject **dict; + dict = _PyObject_GetDictPtr(obj); + /* It is possible that the object's dict is not initialized + yet. In this case, we will return None for the state. + We also return None if the dict is empty to make the behavior + consistent regardless whether the dict was initialized or not. + This make unit testing easier. */ + if (dict != NULL && *dict != NULL && PyDict_Size(*dict) > 0) { + state = *dict; } - PyErr_Clear(); - - if (required && obj->ob_type->tp_itemsize) { + else { + state = Py_None; + } + Py_INCREF(state); + } + + 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 (obj->ob_type->tp_dictoffset) + basicsize += sizeof(PyObject *); + if (obj->ob_type->tp_weaklistoffset) + basicsize += sizeof(PyObject *); + if (slotnames != Py_None) + basicsize += sizeof(PyObject *) * Py_SIZE(slotnames); + if (obj->ob_type->tp_basicsize > basicsize) { + Py_DECREF(slotnames); + Py_DECREF(state); PyErr_Format(PyExc_TypeError, "can't pickle %.200s objects", Py_TYPE(obj)->tp_name); return NULL; } - - { - PyObject **dict; - dict = _PyObject_GetDictPtr(obj); - /* It is possible that the object's dict is not initialized - yet. In this case, we will return None for the state. - We also return None if the dict is empty to make the behavior - consistent regardless whether the dict was initialized or not. - This make unit testing easier. */ - if (dict != NULL && *dict != NULL && PyDict_Size(*dict) > 0) { - state = *dict; - } - else { - state = Py_None; - } - Py_INCREF(state); - } - - slotnames = _PyType_GetSlotNames(Py_TYPE(obj)); - if (slotnames == NULL) { + } + + if (slotnames != Py_None && Py_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 (obj->ob_type->tp_dictoffset) - basicsize += sizeof(PyObject *); - if (obj->ob_type->tp_weaklistoffset) - basicsize += sizeof(PyObject *); - if (slotnames != Py_None) - basicsize += sizeof(PyObject *) * Py_SIZE(slotnames); - if (obj->ob_type->tp_basicsize > basicsize) { + slotnames_size = Py_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 (value == NULL) { + Py_DECREF(name); + if (!PyErr_ExceptionMatches(PyExc_AttributeError)) { + goto error; + } + /* It is not an error if the attribute is not present. */ + PyErr_Clear(); + } + 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 != Py_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, - "can't pickle %.200s objects", - Py_TYPE(obj)->tp_name); return NULL; } } - if (slotnames != Py_None && Py_SIZE(slotnames) > 0) { - PyObject *slots; - Py_ssize_t slotnames_size, i; - - slots = PyDict_New(); - if (slots == NULL) { + /* If we found some slot attributes, pack them in a tuple along + the original attribute dictionary. */ + if (PyDict_Size(slots) > 0) { + PyObject *state2; + + 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 = Py_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 (value == NULL) { - Py_DECREF(name); - if (!PyErr_ExceptionMatches(PyExc_AttributeError)) { - goto error; - } - /* It is not an error if the attribute is not present. */ - PyErr_Clear(); - } - 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 != Py_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_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_CallObject(getstate, NULL); - Py_DECREF(getstate); - if (state == NULL) - return NULL; - } + Py_DECREF(slots); + } + Py_DECREF(slotnames); return state; } +static PyObject * +object_getstate(PyObject *self, PyObject *unused) +{ + return default_getstate(self, 0); +} + +PyObject * +_PyObject_GetState(PyObject *obj) +{ + _Py_IDENTIFIER(__getstate__); + + return _PyObject_CallMethodId(obj, &PyId___getstate__, NULL); +} + Py_LOCAL(int) _PyObject_GetNewArguments(PyObject *obj, PyObject **args, PyObject **kwargs) { @@ -4116,9 +4113,11 @@ Py_LOCAL(int) static PyObject * reduce_newobj(PyObject *obj) { + _Py_IDENTIFIER(__getstate__); PyObject *args = NULL, *kwargs = NULL; PyObject *copyreg; PyObject *newobj, *newargs, *state, *listitems, *dictitems; + PyObject *getstate; PyObject *result; int hasargs; @@ -4186,8 +4185,29 @@ reduce_newobj(PyObject *obj) } } - state = _PyObject_GetState(obj, - !hasargs && !PyList_Check(obj) && !PyDict_Check(obj)); + getstate = _PyObject_GetAttrId(obj, &PyId___getstate__); + if (getstate == NULL || + (PyCFunction_Check(getstate) && + PyCFunction_GET_SELF(getstate) == obj && + PyCFunction_GET_FUNCTION(getstate) == (PyCFunction) object_getstate)) { + if (getstate == NULL) { + if (!PyErr_ExceptionMatches(PyExc_AttributeError)) { + Py_DECREF(newobj); + Py_DECREF(newargs); + return NULL; + } + else { + PyErr_Clear(); + } + } + state = default_getstate(obj, + !(hasargs || PyList_Check(obj) || PyDict_Check(obj))); + } + else { + state = PyObject_CallObject(getstate, NULL); + } + Py_XDECREF(getstate); + if (state == NULL) { Py_DECREF(newobj); Py_DECREF(newargs); @@ -4414,6 +4434,8 @@ static PyMethodDef object_methods[] = { PyDoc_STR("helper for pickle")}, {"__reduce__", object_reduce, METH_VARARGS, PyDoc_STR("helper for pickle")}, + {"__getstate__", object_getstate, METH_NOARGS, + PyDoc_STR("helper for pickle")}, {"__subclasshook__", object_subclasshook, METH_CLASS | METH_VARARGS, object_subclasshook_doc}, {"__format__", object_format, METH_VARARGS,