Closed hulsed closed 2 months ago
You can write as follows:
>>> ex.__defaults__['x']
3.0
>>> ex.x = ex.__defaults__['x']
>>> ex.x
3.0
May be the following prototype for reset could be solve the problem:
def reset(o, name):
from recordclass._dataobject import Factory
copy_default = o.__options__['copy_default']
val = o.__defaults__[name]
if val is not None:
if type(val) is Factory:
val = Factory(val)
elif copy_default:
if isinstance(val, list):
val = val[:]
elif isinstance(val, (dict, set)):
val = val.copy()
elif hasattr(o, '__copy__'):
val = val.__copy__():
setattr(o, name, val)
This recipe should work starting 0.21.1
The issue with using __defaults__
is that the dict is a class variable. So If I have two different initial values in two different objects, both will reset to the same value instead of their independent values.
Ideally there should be an option to set it as a (ideally immutable post-object instantiation) object variable.
So there is a case when class has user defined __init__
and you want to reset attributes to their initial values which they have after first instance initialization?
Something like that. Several of the classes I've build off of recordclass
have a defined __init__
but the goal would be to hold the initial field values rather than those custom arguments.
The idea would be that if I do something like:
ex1 = ExampleState(3.0, 2.0)
ex2 = ExampleState(4.0, 1.0)
I could then get some independent, hidden property that I could reset back to, e.g.:
>>> ex1.__initial__
(3.0, 2.0)
>>> ex2.__initial__
(4.0, 1.0)
I'd imagine this sort of thing would add considerable memory overhead, so it should be an optional feature obviously.
Is this recipe suitable for the problem?
from recordclass import dataobject, asdict, update
class State:
def get_state(self):
return asdict(self)
def set_state(self, state):
update(self, **state)
class Point(dataobject, State):
x:int
y:int
a = Point(1,2)
state = a.get_state()
print(a)
a.x = 100
a.y = 200
print(a)
a.set_state(state)
print(a)
Yes, but I would like to be able to have state be a property of the class so it is all contained in a single object.
Here is another variant:
class State:
def save_state(self):
self.__state__ = {nm:getattr(self, nm) for nm in self.__fields__ if nm != '__state__'}
def restore_state(self):
for nm,val in self.__state__.items():
setattr(self, nm, val)
class Point(dataobject, State):
x:int
y:int
__state__:dict = None
a = Point(1,2)
a.save_state()
print(a)
a.x = 100
a.y = 200
print(a)
a.restore_state()
print(a)
Another variant with state as tuple:
class State:
def save_state(self):
self._state = tuple(getattr(self, nm) for nm in self.__fields__[:-1])
def restore_state(self, state):
for nm, val in zip(self.__fields__[:-1], self._state):
setattr(self, nm, val)
class Point(dataobject, State):
x:int
y:int
_state:tuple
a = Point(1,2)
print(a)
state = a.save_state()
print(a)
a.x = 100
a.y = 200
print(a)
a.restore_state(state)
print(a)
One can also save instance states in the class attribute. For example:
from typing import ClassVar
class State:
__instance_states__: ClassVar[dict] = {}
@classmethod
def clear_states(cls):
cls.__instance_states__ = {}
def del_state(self):
if id(self) in self.__instance_states__:
del self.__instance_states__[id(self)]
def save_state(self):
self.__instance_states__[id(self)] = tuple(getattr(self, nm) for nm in self.__fields__)
def restore_state(self):
_state = self.__instance_states__.get(id(self), ())
for nm, val in zip(self.__fields__, _state):
setattr(self, nm, val)
class Point(dataobject, State):
x:int
y:int
print(Point.__mro__)
a = Point(1,2)
print(a)
a.save_state()
print(a)
a.x = 100
a.y = 200
print(a)
a.restore_state()
print(a)
This is all helpful, I will need to investigate which will work best for my use-case
One addition to the previous post. It's possible to use __del__
method for automatic cleanup saved state. For example, one can add __del__
:
from typing import ClassVar
class State:
__instance_states__: ClassVar[dict] = {}
@classmethod
def clear_states(cls):
cls.__instance_states__ = {}
def del_state(self):
if id(self) in self.__instance_states__:
del self.__instance_states__[id(self)]
def save_state(self):
self.__instance_states__[id(self)] = tuple(getattr(self, nm) for nm in self.__fields__)
def restore_state(self):
_state = self.__instance_states__.get(id(self), ())
for nm, val in zip(self.__fields__, _state):
setattr(self, nm, val)
def __del__(self):
if id(self) in self.__instance_states__:
del self.__instance_states__[id(self)]
class Point(dataobject, State):
x:int
y:int
a = Point(1,2)
print(a)
a.save_state()
print(State.__instance_states__)
print(a)
a.x = 100
a.y = 200
print(a)
a.restore_state()
print(a)
del a
print(State.__instance_states__)
I've found my own solution to this that doesn't involve the data structure itself. In my use-case, it makes more sense to hold these initial non-default values externally as opposed to making them a part of the recordclass itself.
What I would like to do is have a class that (in addition to default arguments) that has initial arguments which can be accessed and reset to if needed.
For example, if I do:
I'd like to be able to get back to or access the initial value somehow, e.g.:
This would be really helpful for modelling dynamical systems, where sometimes we might want to reset to an initial state to run an additional simulation.