Skip to content

Commit daf6f4a

Browse files
author
Anselm Kruis
committed
Stackless issue python#284: Update pickling of functions
Update the pickling code to serialize all attributes of function objects.
1 parent 676d294 commit daf6f4a

File tree

3 files changed

+109
-30
lines changed

3 files changed

+109
-30
lines changed

Stackless/changelog.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ What's New in Stackless 3.X.X?
99

1010
*Release date: 20XX-XX-XX*
1111

12+
- https://github.com/stackless-dev/stackless/issues/284
13+
Stackless supports pickling functions. But previous versions of Stackless
14+
only pickled the following attributes of a function-object: '__code__',
15+
'__globals__', '__name__', '__defaults__', '__closure__' and '__module__'.
16+
Now Stackless pickles all attributes of a function.
17+
1218
- https://github.com/stackless-dev/stackless/issues/268
1319
Pickled code objects now contain importlib.util.MAGIC_NUMBER. Unpickling
1420
code objects pickled with an incompatible version of Python now creates a

Stackless/pickling/prickelpit.c

Lines changed: 71 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -820,25 +820,42 @@ static int init_celltype(PyObject * mod)
820820
821821
******************************************************/
822822

823-
#define functuplefmt "OOOOOO"
823+
#define functuplefmt_pre38 "OOOOOO"
824+
#define functuplefmt functuplefmt_pre38 "OOOOO"
824825

825826
static PyTypeObject wrap_PyFunction_Type;
826827

827828
static PyObject *
828829
func_reduce(PyFunctionObject * func, PyObject *unused)
829830
{
831+
/* See funcobject.c: some attribute can't be NULL. */
832+
assert(func->func_code);
833+
assert(func->func_globals);
834+
assert(func->func_name);
835+
assert(func->func_doc);
836+
assert(func->func_qualname);
837+
838+
PyObject *dict = PyObject_GenericGetDict((PyObject *)func, NULL);
839+
if (NULL == dict)
840+
return NULL;
841+
830842
PyObject *tup = Py_BuildValue(
831843
"(O()(" functuplefmt "))",
832844
&wrap_PyFunction_Type,
833845
/* Standard function constructor arguments. */
834-
func->func_code != NULL ? func->func_code : Py_None,
835-
func->func_globals != NULL ? func->func_globals : Py_None,
836-
func->func_name != NULL ? func->func_name : Py_None,
846+
func->func_code,
847+
func->func_globals,
848+
func->func_name,
837849
func->func_defaults != NULL ? func->func_defaults : Py_None,
838850
func->func_closure != NULL ? func->func_closure : Py_None,
839-
/* Additional data we need to preserve. */
840-
func->func_module != NULL ? func->func_module : Py_None
851+
func->func_module != NULL ? func->func_module : Py_None,
852+
func->func_kwdefaults != NULL ? func->func_kwdefaults : Py_None,
853+
func->func_doc,
854+
dict,
855+
func->func_annotations != NULL ? func->func_annotations : Py_None,
856+
func->func_qualname
841857
);
858+
Py_DECREF(dict);
842859
return tup;
843860
}
844861

@@ -893,25 +910,58 @@ func_setstate(PyObject *self, PyObject *args)
893910
if (args2 == NULL)
894911
return NULL;
895912

896-
fu = (PyFunctionObject *)
897-
Py_TYPE(self)->tp_new(Py_TYPE(self), args2, NULL);
913+
fu = (PyFunctionObject *) Py_TYPE(self)->tp_new(Py_TYPE(self), args2, NULL);
898914
Py_DECREF(args2);
899-
if (fu != NULL) {
900-
PyFunctionObject *target = (PyFunctionObject *) self;
901-
COPY(fu, target, func_code);
902-
COPY(fu, target, func_globals);
903-
COPY(fu, target, func_name);
904-
COPY(fu, target, func_defaults);
905-
COPY(fu, target, func_closure);
915+
if (fu == NULL)
916+
return NULL;
917+
918+
PyFunctionObject *target = (PyFunctionObject *) self;
919+
COPY(fu, target, func_code);
920+
COPY(fu, target, func_globals);
921+
COPY(fu, target, func_name);
922+
COPY(fu, target, func_defaults);
923+
COPY(fu, target, func_closure);
924+
Py_DECREF(fu);
925+
926+
args2 = PyTuple_GetItem(args, 5);
927+
if (NULL == args2)
928+
return NULL;
929+
Py_INCREF(args2);
930+
Py_XSETREF(target->func_module, args2);
931+
932+
if (PyTuple_GET_SIZE(args) != sizeof(functuplefmt_pre38)-1) {
933+
/* Stackless 3.8 and up */
934+
if (PyTuple_GET_SIZE(args) != sizeof(functuplefmt)-1) {
935+
PyErr_Format(PyExc_IndexError, "function.__setstate__ expects a tuple of length %d", (int)sizeof(functuplefmt)-1);
936+
return NULL;
937+
}
938+
args2 = PyTuple_GET_ITEM(args, 6);
939+
if (PyFunction_SetKwDefaults(self, args2))
940+
return NULL;
906941

907-
Py_XINCREF(PyTuple_GetItem(args, 5));
908-
target->func_module = PyTuple_GetItem(args, 5);
942+
args2 = PyTuple_GET_ITEM(args, 7);
943+
Py_INCREF(args2);
944+
Py_XSETREF(target->func_doc, args2);
909945

910-
Py_DECREF(fu);
911-
Py_INCREF(self);
912-
return self;
946+
args2 = PyTuple_GET_ITEM(args, 8);
947+
if (args2 != Py_None && PyObject_GenericSetDict(self, args2, NULL))
948+
return NULL;
949+
950+
args2 = PyTuple_GET_ITEM(args, 9);
951+
if (PyFunction_SetAnnotations(self, args2))
952+
return NULL;
953+
954+
args2 = PyTuple_GET_ITEM(args, 10);
955+
if(!PyUnicode_Check(args2)) {
956+
PyErr_SetString(PyExc_TypeError, "__qualname__ must be set to a string object");
957+
return NULL;
958+
}
959+
Py_INCREF(args2);
960+
Py_XSETREF(target->func_qualname, args2);
913961
}
914-
return NULL;
962+
963+
Py_INCREF(self);
964+
return self;
915965
}
916966

917967
#undef COPY

Stackless/unittests/test_pickle.py

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -499,12 +499,6 @@ def testSubmoduleImporting(self):
499499
m2 = self.loads(self.dumps(m1))
500500
self.assertEqual(m1, m2)
501501

502-
def testFunctionModulePreservation(self):
503-
# The 'module' name on the function was not being preserved.
504-
f1 = lambda: None # @IgnorePep8
505-
f2 = self.loads(self.dumps(f1))
506-
self.assertEqual(f1.__module__, f2.__module__)
507-
508502

509503
class TestFramePickling(StacklessTestCase):
510504
def test_get_set_reduce_frame(self):
@@ -1215,9 +1209,14 @@ def create_cell(obj):
12151209
self._test(obj, 'cell_contents')
12161210

12171211
def test_function(self):
1218-
def obj():
1219-
pass
1220-
self._test(obj, '__code__', '__globals__', '__name__', '__defaults__', '__closure__')
1212+
v = 123
1213+
def obj(a, b, c=4711, *, d=815):
1214+
"a test function"
1215+
return v
1216+
obj.__annotations__ = {'bla': 'fasel'}
1217+
self._test(obj, '__code__', '__globals__', '__name__', '__defaults__',
1218+
'__closure__', '__module__', '__kwdefaults__', '__doc__',
1219+
'__dict__', '__annotations__', '__qualname__')
12211220

12221221
def test_frame(self):
12231222
obj = sys._getframe()
@@ -1461,6 +1460,30 @@ def test_run_with_wrong_magic(self):
14611460
self.assertEqual(rc, 42)
14621461

14631462

1463+
class TestFunctionPickling(StacklessPickleTestCase):
1464+
def setUp(self):
1465+
super().setUp()
1466+
1467+
def tearDown(self):
1468+
super().tearDown()
1469+
1470+
def testFunctionModulePreservation(self):
1471+
# The 'module' name on the function was not being preserved.
1472+
f1 = lambda: None # @IgnorePep8
1473+
f2 = self.loads(self.dumps(f1))
1474+
self.assertEqual(f1.__module__, f2.__module__)
1475+
1476+
def test_unpickle_pre38(self):
1477+
def obj():
1478+
pass
1479+
1480+
reduced = stackless._stackless._wrap.function.__reduce__(obj)
1481+
obj2 = reduced[0](*reduced[1])
1482+
args = reduced[2][:6]
1483+
obj2.__setstate__(args)
1484+
self.assertIs(type(obj2), type(obj))
1485+
1486+
14641487
if __name__ == '__main__':
14651488
if not sys.argv[1:]:
14661489
sys.argv.append('-v')

0 commit comments

Comments
 (0)