在Python 2.7中定义一个类似“属性”的装饰器

时间:2012-05-28 16:21:01

标签: python decorator

我正在尝试编写一个像@property一样的装饰器,但遇到了一些问题。

class Dec(object):
  def __init__(self, fn):
    self._fn = fn
    self._before = None
  @property
  def before(self)
    return self._before
  @before.setter
  def before(self, fn):
    self._before = fn
  def __call__(self, *args, **kwargs):
    self._before(*args, **kwargs)
    self._fn(*args, **kwargs)

def withbefore(fn):
  return Dec(fn)

它是一个简单的链接装饰器。 @property / @。setter语法正是我想要克隆的。

这有效:

@withbefore
def foo():
   ...
@foo.before
def beforefoo():
  ...

但是在课堂上却没有:

class Weee(object):
    @withbefore
    def do_stuff(self):
      pass
    @do_stuff.before
    def before_do_stuff(self):
      pass

它引发导入错误。

TypeError: 'NoneType' object is not callable

我如何正确模仿@property /。{setter,getter,deleter}?

2 个答案:

答案 0 :(得分:2)

实际上,它引发了一个TypeError。

无论如何,在使用函数运行装饰器时我也遇到了同样的错误。之所以发生这种情况,是因为当您使用@foo.before修饰函数时,它将使用修饰函数作为参数调用self._before函数。由于self._beforeNone,因此会引发错误。

它有各种解决方案。我最喜欢的是设置一个不同的默认值self._before - 一个设置self._before值的函数:

class Dec(object):
  def __init__(self, fn):
    self._fn = fn
    def setbefore(b):
        self._before = b
    self._before = self.default_before = setbefore

当然,在调用Dec对象时不应调用此函数,因此我们更改了__call__方法:

  def __call__(self, *args, **kwargs):
      if self._before != self.default_before:
         self._before(*args, **kwargs)
      self._fn(*args, **kwargs)

答案 1 :(得分:1)

真诚地,我认为你会更好:

from functools import wraps

def withbefore(fn):
    def dec(bef):
        fn._before_fn = bef
        return bef

    @wraps(fn)
    def _wrapper(*args, **kwargs):
        fn._before_fn(*args, **kwargs)
        return fn(*args, **kwargs)

    _wrapper.before = dec
    return _wrapper

它更紧凑,更Pythonic,应该适用于所有情况。