简体   繁体   English

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

[英]Extending @property.setter decorator in Python

I'm writing multiple classes with a common logic added to a bunch of attributes.我正在编写多个类,并将公共逻辑添加到一堆属性中。 This is a simplified version of the code:这是代码的简化版本:

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 always has similar form, and I would like to encapsulate it with a decorator. perform_action总是有类似的形式,我想用装饰器封装它。 Essentially I'm looking for a way to write something like this:本质上我正在寻找一种方法来写这样的东西:

# ... 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

Is it possible to extend @property or @prop.setter to achieve this?是否可以扩展@property@prop.setter来实现这一点?

My point wasn't to copy the property implementation, that is very generic on purpose , which probably isn't needed if your logic is always the same for your getter and setter.我的意思不是复制property实现,这是故意非常通用的,如果您的 getter 和 setter 的逻辑始终相同,则可能不需要。 So it would be better to do something like:所以最好做类似的事情:

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

And this way you avoid the boilerplate when writing your various classes instead of repeating it in various getters/setters in various classes.这样,您在编写各种类时就可以避免样板,而不是在各种类的各种 getter/setter 中重复它。

Thanks to @juanpa.arrivillaga I came up with this solution implementing my own descriptor:感谢@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

While copying and pasting the reference code of property and making minor modifications would work as demonstrated by your answer, in the interest of code reuse you can subclass the property class and call super() to access methods of the parent class instead.虽然复制和粘贴property的参考代码并进行少量修改,但为了代码重用,您可以将property class 子类化并调用super()来访问父 class 的方法。

Also, the setter function in your implementation is unnecessarily instantiating a new instance of MyProperty , when it can reuse the current object by returning self._setter :此外,您的实现中的setter function 不必要地实例化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

so that:以便:

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'

outputs:输出:

bar foo

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM