我有一个包含多个属性的类实例:
class MyClass(object):
@property
def func(self):
return [1,4,5]
@property
def func2(self):
return 6
我想从用户提供的新方法动态更改属性,例如:
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])
这样可行,但它会改变type(obj)
,这会弄乱其他一些东西。执行以下操作不会:
cls = type(obj)
new_cls = type(cls.__name__, (cls,), {})
obj.__class__ = new_cls
但是,我无法弄清楚如何在new_cls中正确获取上面的eval(code)
。关于如何解决这个问题的任何想法?
我也试过monkeypatching属性:
def patch_fun(code):
def patched_fun(self):
eval(code)
return patched_fun
patched_fun = patch_fun(code)
setattr(cls, name, property(patched_fun))
或绑定方法:
patched_fun = patch_fun(code).__get__(obj, type(obj))
setattr(cls, name, property(patched_fun))
(我无法从这些中找到答案: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
答案 0 :(得分:1)
由于潜在的安全隐患,我会避免使用eval。
如果没有eval,你就会这样做:
def patch_property(name, new_return):
def _new_property(self):
return new_return
setattr(obj.__class__, name, property(_new_property))
演示:
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]
这当然会改变这个类中的所有对象。
调查metaclasses做这类事情。
编辑,此版本的patch_property
采用自定义可调用:
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]