Reputation: 1
I have a class instance with several properties:
class MyClass(object):
@property
def func(self):
return [1,4,5]
@property
def func2(self):
return 6
I would like to dynamically change a property from a user supplied new method, for example:
obj = MyClass()
def patch_property(name, new_return):
source = '@property\ndef %s(self):\n return %s' % (parameter_name, new_return)
code = compile(source, file_name, 'exec')`
class SubMyClass(MyClass):
eval(code)
obj.__class__ = SubMyClass
patch_property('func', [6,7,8])
This works, however it changes type(obj)
, which messes up some other stuff. Doing the following does not:
cls = type(obj)
new_cls = type(cls.__name__, (cls,), {})
obj.__class__ = new_cls
However, I can't figure out how to properly get the eval(code)
from above in the new_cls. Any ideas on how to solve this?
I also tried monkeypatching the property:
def patch_fun(code):
def patched_fun(self):
eval(code)
return patched_fun
patched_fun = patch_fun(code)
setattr(cls, name, property(patched_fun))
or the bound method:
patched_fun = patch_fun(code).__get__(obj, type(obj))
setattr(cls, name, property(patched_fun))
(I couldn't figure it out from these: Dynamically adding a property to a class , Dynamically adding @property in python, Monkey Patching an attribute within a class, Monkey patching a @property , Python: changing methods and attributes at runtime
Upvotes: 0
Views: 223
Reputation: 5805
I would avoid using eval due to the potential security implications.
This do what you are after without eval:
def patch_property(name, new_return):
def _new_property(self):
return new_return
setattr(obj.__class__, name, property(_new_property))
Demo:
In [39]: class MyClass:
...: pass
...:
In [40]: obj = MyClass()
In [41]: type(obj)
Out[41]: __main__.MyClass
In [42]: patch_property('func', [6,7,8])
In [43]: type(obj)
Out[43]: __main__.MyClass
In [44]: obj.func
Out[44]: [6, 7, 8]
This will of course change all objects from this class.
Look into metaclasses for doing this sort of stuff.
Edit, this version of patch_property
takes a custom callable:
In [105]: class MyClass(object):
...: def __init__(self):
...: self.a = 10 #accounts for attribute referenced
...:
...: @property
...: def func(self):
...: return [1,4,5]
...:
...: @property
...: def func2(self):
...: return 6
In [106]: def patch_property(name, new_return):
...: def _new_property(self):
...: return new_return
...:
...: setattr(obj.__class__, name, property(new_return if callable(new_return) else _new_property))
In [107]: def custom_func(self):
...: x = self.a * 4
...: z = x * 9
...: return z
In [108]: obj = MyClass()
In [109]: patch_property('func', custom_func)
In [110]: obj.func
Out[110]: 360
In [111]: patch_property('func', [4, 5, 6])
In [112]: obj.func
Out[112]: [4, 5, 6]
Upvotes: 1