Search code examples
pythondecorator

How to pass self attributes to a decorator?


I have a decorator for an object method:

def wrapper(func):
  def _wrapper(self, *a, **kw):
    print(self.foo)
    return func(self, *a, **kw)
  return _wrapper


class Bar:
  foo = 1
  
  @wrapper
  def baz(self):
    print(2)

# This works as expected :)
In [2]: Bar().baz()
1
2

I want to add another level of wrapping around this to allow parameters:

def wrapper(bla):
  def _wrapper(func):
    def __wrapper(self, *a, **kw):
      print(self.foo + bla)
      return func(self, *a, **kw)
    return __wrapper
  return _wrapper

class Bar:
  foo = 1

  @wrapper(1)
  def baz(self):
    print(2)

# which also works great
In [4]: Bar().baz()
2
2

Now, what I want to be able to do is pass a self attribute as a wrapper parameter, i.e. call it with something like this

class Bar:
  def __init__(self):
    self._foo = 1

  @wrapper(self._foo)
  def baz(self):
    pass

I see 2 options:

  1. make self._foo global
  2. don't pass self._foo, and make the decorator access self._foo

Both are bad. (global can be changed from another place without my knowledge, and making the decorator access self._... means the decorator knows the class it operates on) Is there a good way to do this?


Solution

  • This example works just fine (You can pick _foo or _bar) and the wrapper itself knows nothing of Bar nor its members:

    def wrapper(bla):
      def _wrapper(func):
        def __wrapper(self, *a, **kw):
          print(getattr(self, bla))
          return func(self, *a, **kw)
        return __wrapper
      return _wrapper
    
    class Bar:
      def __init__(self):
        self._foo = 1
        self._bar = 2
    
      @wrapper("_bar")  # pick _foo or _bar here
      def baz(self):
        pass
    
    
    Bar().baz()
    

    Output: 2

    The wrapper decorator can be located in another module, or as part of a library and Bar as the client will be unknown to the decorator.