teeler
teeler

Reputation: 13

Defining a "property"-like decorator in Python 2.7

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} ?

Upvotes: 1

Views: 1613

Answers (2)

ubik
ubik

Reputation: 4560

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.

Upvotes: 1

brandizzi
brandizzi

Reputation: 27050

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)

Upvotes: 2

Related Questions