【问题标题】:Dynamically add @property to python class from user defined string从用户定义的字符串动态添加@property到python类
【发布时间】:2018-10-31 18:49:30
【问题描述】:

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

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)。关于如何解决这个问题的任何想法?

我也尝试过猴子修补属性:

    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

【问题讨论】:

  • compile() 在您的代码中似乎存在大规模的安全漏洞。在之前的帖子中尝试过使用 setatrr 和 lambda 函数,感觉更合理。

标签: python python-2.7 class dynamic properties


【解决方案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]

【讨论】:

  • 那么我想要一个多行函数而不是 new_return 怎么样。例如'x = self.a * 4\nz = x *9\nreturn z'
  • @jasp116 然后将整个_new_property 作为参数传递,而不仅仅是new_return
  • @jasp116 zvone 所说的,让函数接受一个可调用的。我编辑了一个接受值或可调用的版本。
  • @salparadise 不幸的是,setattr 修补方法不起作用,因为必须从 MyClass 内部创建该方法才能使其他一些东西起作用。就像 MySubClass 方法一样,但在 MyClass 中以某种方式..
  • @zvone 如果它是我上面评论中的字符串,它将如何工作?
猜你喜欢
  • 2011-02-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-21
  • 2020-11-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多