Расширение декоратора @ property.setter в Python - PullRequest
1 голос
/ 06 мая 2020

Я пишу несколько классов с общим c логом, добавленным к множеству атрибутов. Это упрощенная версия кода:

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 ]

2 голосов
/ 06 мая 2020

Я не собирался копировать реализацию property, то есть очень общий c специально , который, вероятно, не нужен, если ваш logi c всегда одинаков для вашего геттер и сеттер. Поэтому было бы лучше сделать что-то вроде:

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

И таким образом вы избежите шаблонного при написании различных классов вместо того, чтобы повторять его в различных геттерах / сеттерах в разных классах.

1 голос
/ 06 мая 2020

Хотя копирование и вставка ссылочного кода 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
1 голос
/ 06 мая 2020

Благодаря @ 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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...