【问题标题】:Extending @property.setter decorator in Python在 Python 中扩展 @property.setter 装饰器
【发布时间】:2020-08-20 16:48:05
【问题描述】:

我正在编写多个类,并将一个公共逻辑添加到一堆属性中。这是代码的简化版本:

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 来实现这一点?

【问题讨论】:

  • 您可能应该只编写自己的描述符,而不是尝试扩展property 整点或property 是为您提供一个非常常见的用例的实现,用于更复杂的事情, 你应该只写你自己的描述符.
  • @juanpa.arrivillaga 谢谢你的建议,我没想到!我仍然想扩展property(或实现我自己的版本,但我不确定它是通过哪种机制工作的,它与我之前使用的简单装饰器不同)因为它允许保留所有与属性在代码中靠得很近。
  • 这就是我所说的“实现你自己的描述符”的意思。它是一个 decorator 的事实并不真正相关。这不是它的基本工作方式,它之所以有效,是因为装饰器返回了一个描述符对象。
  • 谢谢@juanpa.arrivillaga!这正是我所需要的。我设法实现了我需要的行为并在下面发布了答案。
  • @juanpa.arrivillaga OP 要求的是在 property 类已经完成的功能之上执行额外的功能,这正是类继承的目的。我已经包含了这样一个实现供参考。

标签: python decorator python-decorators


【解决方案1】:

我的意思不是复制 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 中重复它。

【讨论】:

  • 好吧,实际上在 getter/setter 中发生的事情是不可推广的(为了简单起见,我使用了简单的隐藏字段访问),所以子类化 property 更适合我的情况。
【解决方案2】:

虽然复制和粘贴property 的参考代码并进行少量修改会如您的回答所证明的那样工作,但为了代码重用,您可以将property 类子类化并调用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'

输出:

bar foo

【讨论】:

    【解决方案3】:

    感谢@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
    
    

    【讨论】:

      猜你喜欢
      • 2019-09-01
      • 1970-01-01
      • 2019-05-17
      • 1970-01-01
      • 1970-01-01
      • 2021-12-01
      • 2016-08-18
      • 2013-01-22
      • 2020-06-26
      相关资源
      最近更新 更多