1

I'm trying to write a decorator that works like @property, but running into some problems.

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)

Its a simple chaining decorator. The @property/@.setter syntax is exactly what I'm trying to clone.

This works:

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

But on a class it doesn't:

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

It raises an import error.

TypeError: 'NoneType' object is not callable

How can i correctly emulate @property/.{setter,getter,deleter} ?

teeler
  • 13
  • 4

2 Answers2

2

Actually, it raises a TypeError.

Anyway, I got the same error when running your decorator with functions, too. It happens because, when you decorate a function with @foo.before, it will call the self._before function with the decorated function as parameter. Since self._before is None, it will raise the error.

There are various solutions for it. My favorite is to set a different default value to self._before - a function which will set the self._before value:

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

Of course, this function should not be called when the Dec object is called so we change the __call__ method:

  def __call__(self, *args, **kwargs):
      if self._before != self.default_before:
         self._before(*args, **kwargs)
      self._fn(*args, **kwargs)
brandizzi
  • 26,083
  • 8
  • 103
  • 158
  • 1
    Ah, i got ya. That whole @property part is rather stupid i think then, its much cleaner without it. Also, once thats in place, I still needed to do [this](http://stackoverflow.com/questions/5469956/python-decorator-self-is-mixed-up) to ensure self is correct. Thanks! – teeler May 28 '12 at 17:17
  • @teeler wow, this linked answer to the second problem is pretty amazing! – brandizzi May 28 '12 at 17:44
1

Sincerely, I think you'd be better off with:

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

It is more compact, more Pythonic and should work OK for all cases.

ubik
  • 4,440
  • 2
  • 23
  • 29