Ascertaining why an object cannot be pickled
I am getting object t
,, from api type Object
. I can't uncover it, getting the error:
File "p.py", line 55, in <module>
pickle.dump(t, open('data.pkl', 'wb'))
File "/usr/lib/python2.6/pickle.py", line 1362, in dump
Pickler(file, protocol).dump(obj)
File "/usr/lib/python2.6/pickle.py", line 224, in dump
self.save(obj)
File "/usr/lib/python2.6/pickle.py", line 313, in save
(t.__name__, obj))
pickle.PicklingError: Can't pickle 'Object' object: <Object object at 0xb77b11a0>
When I do the following:
for i in dir(t): print(type(i))
I only get string objects:
<type 'str'>
<type 'str'>
<type 'str'>
...
<type 'str'>
<type 'str'>
<type 'str'>
How can I print the contents of my object Object
to understand why it cannot be pickled?
It's also possible that the object contains C pointers to QT objects, in which case it didn't make sense for me to sort the object. But again I would like to see the internal structure of the object in order to establish this.
source to share
You might want to read the python docs and check your API class afterwards Object
.
As far as the "internal structure of an object" is concerned, typically the instance attributes are stored in an attribute __dict__
(and since no class attributes are fetching you only care about the instance attributes) - but note that you will also have to recursively inspect __dict__
for each attribute.
source to share
I would use dill
one that has tools to investigate what inside an object makes your target non-stealable. See this answer for an example: A good example of BadItem in the Dill module , and also this question for an example of real-world use of detection tools: pandas.algos._return_false raises PicklingError with dill. dump_session for CentOS .
>>> import dill
>>> x = iter([1,2,3,4])
>>> d = {'x':x}
>>> # we check for unpicklable items in d (i.e. the iterator x)
>>> dill.detect.baditems(d)
[<listiterator object at 0x10b0e48d0>]
>>> # note that nothing inside of the iterator is unpicklable!
>>> dill.detect.baditems(x)
[]
However, the most common starting point is to use trace
:
>>> dill.detect.trace(True)
>>> dill.detect.errors(d)
D2: <dict object at 0x10b8394b0>
T4: <type 'listiterator'>
PicklingError("Can't pickle <type 'listiterator'>: it not found as __builtin__.listiterator",)
>>>
dill
also has functions for keeping track of pointers and object references, so you can build a hierarchy of how objects refer to each other. See: https://github.com/uqfoundation/dill/issues/58
Alternatively, there are also: cloudpickle.py and debugpickle.py, which for the most part are no longer being developed. I am the author dill
and hope to soon unify any functionality in these codes that is missing from dill
.
source to share
I tried Dill but that didn't explain my problem. Instead, I used the following code from https://gist.github.com/andresriancho/15b5e226de68a0c2efd0 which encountered an error in my override __getattribute__
:
def debug_pickle(instance):
"""
:return: Which attribute from this object can't be pickled?
"""
attribute = None
for k, v in instance.__dict__.iteritems():
try:
cPickle.dumps(v)
except:
attribute = k
break
return attribute
Edit: Here's my code reproduced using pickle and cPickle:
class myDict(dict):
def __getattribute__(self, item):
# Try to get attribute from internal dict
item = item.replace("_", "$")
if item in self:
return self[item]
# Try super, which may leads to an AttribueError
return super(myDict, self).__getattribute__(item)
myd = myDict()
try:
with open('test.pickle', 'wb') as myf:
cPickle.dump(myd, myf, protocol=-1)
except:
print traceback.format_exc()
try:
with open('test.pickle', 'wb') as myf:
pickle.dump(myd, myf, protocol=-1)
except:
print traceback.format_exc()
Output:
Traceback (most recent call last):
File "/Users/myuser/Documents/workspace/AcceptanceTesting/ingest.py", line 35, in <module>
cPickle.dump(myd, myf, protocol=-1)
UnpickleableError: Cannot pickle <class '__main__.myDict'> objects
Traceback (most recent call last):
File "/Users/myuser/Documents/workspace/AcceptanceTesting/ingest.py", line 42, in <module>
pickle.dump(myd, myf, protocol=-1)
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 1370, in dump
Pickler(file, protocol).dump(obj)
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 224, in dump
self.save(obj)
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 313, in save
(t.__name__, obj))
PicklingError: Can't pickle 'myDict' object: {}
You will see that the reason is because the attribute names are mangled __getattribute__
source to share
Here's an extension of Alastair's solution , in Python 3.
Is he:
is recursive, for working with complex objects, where the problem can be multilevel.
The output is on the form
.x[i].y.z....
so you can see which members were called to resolve the problem. With help,dict
it just prints instead[key/val type=...]
, as key or value problems can arise, making it difficult (but not impossible) to access a specific key or value indict
.allows for more types, in particular
list
,tuple
anddict
that need to be handled separately because they have no attributes__dict__
.returns all problems, not just the first one.
def get_unpicklable(instance, exception=None, string='', first_only=True):
"""
Recursively go through all attributes of instance and return a list of whatever
can't be pickled.
Set first_only to only print the first problematic element in a list, tuple or
dict (otherwise there could be lots of duplication).
"""
problems = []
if isinstance(instance, tuple) or isinstance(instance, list):
for k, v in enumerate(instance):
try:
pickle.dumps(v)
except BaseException as e:
problems.extend(get_unpicklable(v, e, string + f'[{k}]'))
if first_only:
break
elif isinstance(instance, dict):
for k in instance:
try:
pickle.dumps(k)
except BaseException as e:
problems.extend(get_unpicklable(
k, e, string + f'[key type={type(k).__name__}]'
))
if first_only:
break
for v in instance.values():
try:
pickle.dumps(v)
except BaseException as e:
problems.extend(get_unpicklable(
v, e, string + f'[val type={type(v).__name__}]'
))
if first_only:
break
else:
for k, v in instance.__dict__.items():
try:
pickle.dumps(v)
except BaseException as e:
problems.extend(get_unpicklable(v, e, string + '.' + k))
# if we get here, it means pickling instance caused an exception (string is not
# empty), yet no member was a problem (problems is empty), thus instance itself
# is the problem.
if string != '' and not problems:
problems.append(
string + f" (Type '{type(instance).__name__}' caused: {exception})"
)
return problems
source to share