I see that the methods defined in extension at global level are not pickleable. The functions defined in a module are pickleable by name reference in case of pure Python modules. Following example is take from the Demo directory which comes as a part of the source code. I am working on a linux machine and trying to write extensions to Python 3.7.
In [1]: import simple In [2]: simple Out[2]: <module 'simple' from '<pycxx_dir>/obj/simple.so'> In [3]: pickle.dumps(simple.func) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-16-2284e569486f> in <module> ----> 1 pickle.dumps(simple.func) TypeError: can't pickle PyCapsule objects
This is because simple.func.reduce returns a tuple including a PyCapsule object which is not pickleable.
In [1]: simple.func.__reduce__() Out[1]: (<function getattr>, ((<capsule object NULL at 0x7f8851d77cf0>, <capsule object NULL at 0x7f8851d77450>), 'func'))
Also, the name of the function defined in extension module is wrong.
In [2]: simple.func Out[2]: <function simple.tuple.func>
I can repo what you see.
However I'm not clear that pickle support is usual for C++ extension functions.
I did try with the PyQt5 code and cannot pickle its functions either.
In the case of class instances the problem will get harder. Without all the help
coming from the objects it is not possible to know what state to pickle and even if it makes sense to pickle, think of an object that holds a database connection.
The docs do say that not all objects can be pickled, and that the code is not pickled at all.
If you implement a class that has the
__getstate__
and__setstate__
methods on a class you may have more success. I have no done that experiment myself.>>> from PyQt5 import QtCore
>>> import pickle
>>> pickle.dumps(x.majorVersion)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't pickle QVersionNumber objects
>>> x.majorVersion
<built-in method majorVersion of QVersionNumber object at 0x10e1d8748>
>>> x.majorVersion()
0
Last edit: Barry Alan Scott 2019-05-29
The problem is that I should be able to use a function as a part of dumping an instance. My use case is to call the function from Python to create an object after doing computations in C++. Thus I need the function to be pickleable.
To answer whether pickle support is usual for C++ extensions functions - extension methods defined using CPython documentation are indeed pickleable. See doc for more details.
Further,
simple.func
should remain a function of the simple module. Pycxx wrongly injects it as a member oftuple
type.Specifically which part of the docs did you want to draw to my attention?
It sounds to me like you want to pickle the return value from the function, not the function?
For your returned object to be picklable its seems that you need to
the pickle protocol as documented in https://docs.python.org/3/library/pickle.html
I suspect that I need to implement METH_CLASS in pycxx to allow the unpickling to work.
I baffled by the "tuple", its not apparent in the PyCXX the code. It will take some debugging to track down where it comes from.
(I tried reading the code of
_sre
to see how it does pickling, but I have found where it's__reduce__
code is implemented yet.I will try to illustrate my use case below.
C++: Say we have an extension called
simple
.Suppose func can't be added as reduce method of the ClassInExtension.
Python:
pickle.dumps(foo)
is what breaks withPycxx+ Python 3
. This works fine withPycxx+Python2
though.In the doc, we define
spam.system
as a method in the extension namedspam
. Pickling of this function works fine in both Python 2 and 3.I tried debugging the Pycxx code. The reason is that we switched to use from
PyCObject_FromVoidPtr
in Python 2 toPyCapsule_New
in Python 3 while initializing functions inExtensionModule::initialize
.Python2:
Python 3:
Last edit: Saim Raza 2019-05-30
The use of PyCapsule is required since python 3.1, that is not the problem in itself.
So if I understand it pickle will pickle the reduce function into the pickle stream so
that the unpickling works.
Do you have a example that fully works to pickle and unpickle in python2?
Can you share the code with me to debug with?
This may be a hard enough problem that I will not have enough free time to fix it quickly,
In the mean time can you solve your pickling problem by side stepping this part of picking?
You could have a function on your object that converted the object into simple python objects
and pickle that that. Then when you unpickle pass that back to a object constructor?
This would be the implementations of
__getstate__
and__setstate__
that youneed anyway.
I had some insperation. See Demo/Python3/pickle_test.py and the changes to Demon/Python3/simple.cxx. Added in commit R414.
Run with:
$ ./build-unlimited-api.sh python3.7
$ PYTHONPATH=obj python3.7 Demo/Pytohn3/pickle_test.py
Seems I only needed to chage the c'tor and add
__reduce__
.Tests in
pickle_tests.py
only test that a class defined in an extension is pickleable. However, the issue here is that any method defined as a part ofsimple
module is unpickleable.Following tests should pass for this bug to be resolved:
I do not understand why pickling the function is required for the use case of pickling objects,
which I have shown can be made to work with no changes.
Until I understand why its a real world problem I do not plan to work towars a fix.
The following example will try to demonstrate a real world use case. I have a class
MyComputations
which delegates heavy computation to C++. The class saves the functions it has to call, and we execute those functions (defined in the extension module) by calling its methoddo_computations
.I have used the
simple
extension module which comes as a part ofpycxx
source distribution to make things simple.OUTPUT:
Now calling
__reduce__
onMyComputations
will return a tuple containing references to the functions passed to the class. Native Python mechanism will try to pickle these functions and is able to do so successfully.We can see that pickling of
mycomp
object works.This is what is actually broke in Python 3 with PYCXX.
Note: We can work-around this problem by pickling only names of the functions passes to
MyComputations
class and importing them from thesimple
module using the names. Following is the implementation which avoids this limitation (bug?) of pycxx in Python 3.This is indeed the gist of what I have done with my code. However, this is a hack. I should be able to treat functions as functions as is supported by native Python.
Please let me know if you need more clarification.
Thanks for taking the time to explain why this matters.
I suspect this is going to take a while for me to understand what has to be changed to make this work.
You should stay with your work around in the mean time.