Динамически добавлять @property в класс Python из пользовательской строки - PullRequest
0 голосов
/ 22 мая 2018

У меня есть экземпляр класса с несколькими свойствами:

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

Однако я не могу понять, как правильно получить eval(code) сверху в new_cls.Любые идеи о том, как решить эту проблему?

Я также пытался установить свойство обезьяны:

    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))

(я не мог понятьэто из этих: Динамическое добавление свойства в класс , Динамическое добавление @property в python , Обезьяна Исправление атрибута в классе , Обезьянаисправление Python @property , : изменение методов и атрибутов во время выполнения

1 Ответ

0 голосов
/ 22 мая 2018

Я бы не использовал 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]

Это, конечно, изменит все объекты этого класса.

Посмотрите на метаклассы , чтобы делать подобные вещи.

Редактировать, эта версия 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]
...