从用户定义的字符串

时间:2018-05-22 05:17:01

标签: python python-2.7 class dynamic properties

我有一个包含多个属性的类实例:

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 pythonMonkey Patching an attribute within a classMonkey patching a @property Python: changing methods and attributes at runtime

1 个答案:

答案 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]