在Python中扩展@ property.setter装饰器

时间:2020-05-05 21:07:44

标签: python decorator python-decorators

我正在编写多个类,这些类具有添加到一堆属性中的通用逻辑。这是代码的简化版本:

class FooAspect:
  _bar_prop = 'bar'

  def __init__(self, bar_value: int):
    self._bar = bar_value

  @property
  def bar(self) -> int:
    return self._bar

  @bar.setter
  def bar(self, value: int) -> None:
    self._bar = value

    perfrom_action(self._bar_prop, value)

perform_action始终具有相似的形式,我想用装饰器将其封装。本质上,我正在寻找一种写这样的东西的方法:

# ... define @my_setter

class FooAspect:
  # ...

  @property
  def bar(self) -> int:
    return self._bar

  @bar.my_setter(key='bar')
  def bar(self, value: int) -> None:
    self._bar = value

是否可以扩展@property@prop.setter来实现这一目标?

3 个答案:

答案 0 :(得分:2)

我的意思不是复制property实现,这是非常通用的,如果您的getter和setter逻辑始终相同,则可能不需要这样做。因此,最好执行以下操作:

def perform_action(key, value):
    print(key, value)

class PerformKeyAction:
    def __init__(self, key):
        self.key = key

    def __get__(self, instance, owner):
        # maybe common getter behavior
        return getattr(instance, self.attr_name)

    def __set__(self, instance, value):
        perform_action(self.key, value)
        return setattr(instance, self.attr_name, value)

    def __set_name__(self, owner, name):
        self.attr_name = f'_{name}'

class FooAspect:
    bar = PerformAction(key='bar')

class BazAspect:
    buzz = PerformAction(key='buzz_key')

class FizzAspect:
    fang = PerformAction(key='fang_key')

这样,您在编写各种类时就避免了样板,而不是在各种类中的各种getter / setter中重复进行。

答案 1 :(得分:1)

由于@ juanpa.arrivillaga,我想出了一个实现自己的描述符的解决方案:

# myproperty.py

class MyProperty:
    def __init__(self, fget=None, fset=None, key=None):
        self.fget = fget
        self.fset = fset
        self.key = key

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")

        perfrom_action(self.key, value)

        self.fset(obj, value)

    def getter(self, fget):
        return type(self)(fget, self.fset, self.key)

    def _setter(self, fset):
        return type(self)(self.fget, fset, self.key)

    def setter(self, key):
        return type(self)(self.fget, self.fset, key=key)._setter
# foo_aspect.py

from myproperty import MyProperty

class FooAspect:
  # ...

  @MyProperty
  def bar(self) -> int:
    return self._bar

  @bar.setter(key='bar')
  def bar(self, value: int) -> None:
    self._bar = value

答案 2 :(得分:1)

复制和粘贴property的参考代码并进行较小的修改可以如您的回答所示,但为了代码重用,您可以继承property类并调用{{1} }来访问父类的方法。

此外,当实现中的super()函数可以通过返回setter重用当前对象时,不必要地实例化MyProperty的新实例:

self._setter

这样:

class MyProperty(property):
    def __set__(self, obj, value):
        super().__set__(obj, value)
        perform_action(self.key, value)

    def _setter(self, fset):
        obj = super().setter(fset)
        obj.key = self.key
        return obj

    def setter(self, key):
        self.key = key
        return self._setter

输出:

class FooAspect:
    @MyProperty
    def bar(self) -> int:
        return self._bar

    @bar.setter(key='bar')
    def bar(self, value: int) -> None:
        self._bar = value

def perform_action(key, value):
    print(key, value)

f = FooAspect()
f.bar = 'foo'
相关问题