I have a rather large project, including a class Foo which recently needed to be updated using the @property
decorator to create custom getter and setter methods.
I also stored several instances of Foo
on my hard drive that at some point I might need to reload. My problem is, that I cannot access the attributes decoreted with property on these old objects.
Consider the following example:
import pickle
# define Class and create instance
class Foo:
def __init__(self):
self.val = 1
foo = Foo()
# dump foo into file
with open("foo.pickle", 'wb') as handle:
pickle.dump(foo, handle, pickle.HIGHEST_PROTOCOL)
# overwrite and add @property in the class definition
class Foo:
def __init__(self):
self._val = "new_foo"
@property
def val(self):
return self._val
@val.setter
def val(self, val):
self._val = val
foo_new = Foo()
print(foo_new.val)
# reload foo
with open("foo.pickle", "rb") as handle:
foo_old = pickle.load(handle)
# try to access attributes
print(foo_old.val)
The last line raises:
NameError: name '_val' is not defined
What options do I have to still access the attributes of my archived instances?
Edit: Changed self.val
to self._val
in the constructor of the second Foo-definition.
The pickle documentation says:
When a class instance is unpickled, its
__init__()
method is usually not invoked.
Which is why the _val
attribute wasn't defined You can workaround that by defining a __new__
method in the replacement Foo
class and setting the instance attribute there:
import pickle
# define Class and create instance
class Foo:
def __init__(self):
self.val = 1
foo = Foo()
# dump foo into file
with open("foo.pickle", 'wb') as handle:
pickle.dump(foo, handle, pickle.HIGHEST_PROTOCOL)
# overwrite and add @property in the class definition
class Foo:
def __new__(cls, val=None):
inst = super().__new__(cls)
inst._val = "new_foo" if val is None else val
return inst
@property
def val(self):
return self._val
@val.setter
def val(self, val):
self._val = val
foo_new = Foo()
print(foo_new.val) # -> new_foo
# reload foo
with open("foo.pickle", "rb") as handle:
foo_old = pickle.load(handle)
print(foo_old.val) # -> new_foo
A possibility is to use a custom Unpickler
, although you would need to keep the old class around (with a different name, hidden if you want) and define the logic to convert an object of the old class to the new one. Here's a basic example:
import pickle
# define Class and create instance
class Foo:
def __init__(self):
self.val = 1
foo = Foo()
# dump foo into file
with open("foo.pickle", 'wb') as handle:
pickle.dump(foo, handle, pickle.HIGHEST_PROTOCOL)
# Old class is kept with a new name
FooOld = Foo
# overwrite and add @property in the class definition
class Foo:
def __init__(self):
self._val = "new_foo"
@property
def val(self):
return self._val
@val.setter
def val(self, val):
self._val = val
foo_new = Foo()
print(foo_new.val)
# Custom Unpickler
class FooOldUnpickler(pickle.Unpickler):
def __init__(self, *args, **kwargs):
super(FooOldUnpickler, self).__init__(*args, **kwargs)
def load(self):
obj = super(FooOldUnpickler, self).load()
if type(obj) is FooOld:
# Object conversion logic
newObj = Foo()
newObj.val = obj.val
obj = newObj
return obj
def find_class(self, module, name):
# Use old class instead of new for loaded objects
if module == __name__ and name == 'Foo':
return FooOld
return super(FooOldUnpickler, self).find_class(module, name)
# reload foo
with open("foo.pickle", "rb") as handle:
# Use custom unpickler
foo_old = FooOldUnpickler(handle).load()
# try to access attributes
print(foo_old.val)
This may be a total hack -- I'm not certain. However, I was able to reconstruct an object pickled from your first "Foo" class using the following code;
import pickle
class Foo:
def __init__(self):
self._val = "new_foo"
@property
def val(self):
try:
return self._val
except AttributeError:
self._val = self.__dict__['val']
self.__dict__.pop('val')
return self._val
@val.setter
def val(self, val):
self._val = val
with open("foo.pickle", "rb") as handle:
foo_old = pickle.load(handle)
print(foo_old.val)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With