@@ -3662,6 +3662,7 @@ save_reduce(PicklerObject *self, PyObject *args, PyObject *obj)
36623662 PyObject * state = NULL ;
36633663 PyObject * listitems = Py_None ;
36643664 PyObject * dictitems = Py_None ;
3665+ PyObject * state_setter = Py_None ;
36653666 PickleState * st = _Pickle_GetGlobalState ();
36663667 Py_ssize_t size ;
36673668 int use_newobj = 0 , use_newobj_ex = 0 ;
@@ -3672,14 +3673,15 @@ save_reduce(PicklerObject *self, PyObject *args, PyObject *obj)
36723673 const char newobj_ex_op = NEWOBJ_EX ;
36733674
36743675 size = PyTuple_Size (args );
3675- if (size < 2 || size > 5 ) {
3676+ if (size < 2 || size > 6 ) {
36763677 PyErr_SetString (st -> PicklingError , "tuple returned by "
3677- "__reduce__ must contain 2 through 5 elements" );
3678+ "__reduce__ must contain 2 through 6 elements" );
36783679 return -1 ;
36793680 }
36803681
3681- if (!PyArg_UnpackTuple (args , "save_reduce" , 2 , 5 ,
3682- & callable , & argtup , & state , & listitems , & dictitems ))
3682+ if (!PyArg_UnpackTuple (args , "save_reduce" , 2 , 6 ,
3683+ & callable , & argtup , & state , & listitems , & dictitems ,
3684+ & state_setter ))
36833685 return -1 ;
36843686
36853687 if (!PyCallable_Check (callable )) {
@@ -3714,6 +3716,15 @@ save_reduce(PicklerObject *self, PyObject *args, PyObject *obj)
37143716 return -1 ;
37153717 }
37163718
3719+ if (state_setter == Py_None )
3720+ state_setter = NULL ;
3721+ else if (!PyCallable_Check (state_setter )) {
3722+ PyErr_Format (st -> PicklingError , "sixth element of the tuple "
3723+ "returned by __reduce__ must be a function, not %s" ,
3724+ Py_TYPE (state_setter )-> tp_name );
3725+ return -1 ;
3726+ }
3727+
37173728 if (self -> proto >= 2 ) {
37183729 PyObject * name ;
37193730 _Py_IDENTIFIER (__name__ );
@@ -3933,11 +3944,32 @@ save_reduce(PicklerObject *self, PyObject *args, PyObject *obj)
39333944 return -1 ;
39343945
39353946 if (state ) {
3936- if (save (self , state , 0 ) < 0 ||
3937- _Pickler_Write (self , & build_op , 1 ) < 0 )
3938- return -1 ;
3939- }
3947+ if (state_setter == NULL ) {
3948+ if (save (self , state , 0 ) < 0 ||
3949+ _Pickler_Write (self , & build_op , 1 ) < 0 )
3950+ return -1 ;
3951+ }
3952+ else {
3953+
3954+ /* If a state_setter is specified, call it instead of load_build to
3955+ * update obj's with its previous state.
3956+ * The first 4 save/write instructions push state_setter and its
3957+ * tuple of expected arguments (obj, state) onto the stack. The
3958+ * REDUCE opcode triggers the state_setter(obj, state) function
3959+ * call. Finally, because state-updating routines only do in-place
3960+ * modification, the whole operation has to be stack-transparent.
3961+ * Thus, we finally pop the call's output from the stack.*/
39403962
3963+ const char tupletwo_op = TUPLE2 ;
3964+ const char pop_op = POP ;
3965+ if (save (self , state_setter , 0 ) < 0 ||
3966+ save (self , obj , 0 ) < 0 || save (self , state , 0 ) < 0 ||
3967+ _Pickler_Write (self , & tupletwo_op , 1 ) < 0 ||
3968+ _Pickler_Write (self , & reduce_op , 1 ) < 0 ||
3969+ _Pickler_Write (self , & pop_op , 1 ) < 0 )
3970+ return -1 ;
3971+ }
3972+ }
39413973 return 0 ;
39423974}
39433975
0 commit comments