JG
JG

Reputation:

How can I use a class instance variable as an argument for a method decorator in Python?

How can I use a class instance variable as an argument for a method decorator in Python? The following is a minimal example shows what I'm trying to do. It obviously fails as the decorator function does not have access to the reference to the instance and I have no idea how to get access to the reference from the decorator.

def decorator1(arg1):
    def wrapper(function):
        print "decorator argument: %s" % arg1
        return function
    return wrapper

class Foo(object):
    def __init__(self, arg1):
        self.var1 = arg1

    @decorator1(self.var1)
    def method1(self):
        print "method1"

foo = Foo("abc")
foo.method1()

Upvotes: 10

Views: 10827

Answers (6)

Kiran Kumar Kotari
Kiran Kumar Kotari

Reputation: 1160

Nested Methods made the trick, I was having a problem to passing self again while using it. Later I wrapped the function assignment with lambda to resolve the issue.

https://gist.github.com/kirankotari/e6959e74316352bfd06afa4d5ab8bd52

Upvotes: 0

mosegui
mosegui

Reputation: 722

Do not try to juggle with decorators for this problem, it is going to make your code very complex and unpleasant to read.

You are attempting to construct a wrapper at class creation time with information that is only going to be available from instantiation time on. You can build the decorator dynamically at instantiation time, but still the outer class level at which you will need to apply the decorator to the methods will not have access to instance variables.

For avoiding the mess that implies to solve this problem with decorators, Python incorporates (from 2.7 on) a dedicated data model for solving this specific type of problem. It is called context manager and you can implement it by just using a generator.

from contextlib import contextmanager

def lock_file(file):
    print('File %s locked' % file)

def unlock_file(file):
    print('File %s unlocked' % file)

@contextmanager
def file_locked(arg1):
    lock_file(arg1)
    yield
    unlock_file(arg1)

class Foo(object):
    def __init__(self, arg1):
        self.var1 = arg1

    def method1(self):
        with file_locked(self.var1) as f:
            print "method1"

foo = Foo("abc")
foo.method1()

Upvotes: 1

S.Lott
S.Lott

Reputation: 391846

Here's how we used to do this in the olden days.

class Foo(object):
    def __init__(self, arg1):
        self.var1 = arg1

    def method1(self):
        self.lock()
        try:
            self.do_method1()
        except Exception:
            pass # Might want to log this
        finally:
            self.unlock()

    def do_method1(self):
        print "method1"

    def lock(self):
        print "locking: %s" % self.arg1

    def unlock(self):
        print "unlocking: %s" % self.arg1

Now, a subclass only needs to o override do_method1 to get the benefits of the "wrapping". Done the old way, without any with statement.

Yes, it's long-winded. However, it doesn't involve any magic, either.

Upvotes: 1

UncleZeiv
UncleZeiv

Reputation: 18488

The decorator is executed when the class is defined, so you can't pass an instance variable to it.

Upvotes: 0

Arpegius
Arpegius

Reputation: 5887

Your “warper” function is actually a decorator, rather than a warper. Your “decorator1” function is a decorator constructor. If you want to have access to self.var1 in runtime you have to make a warper not decorator:

def decorator(function):
  def wrapper(self,*args,**kwargs):
    print "Doing something with self.var1==%s" % self.var1
    return function(self,*args,**kwargs)
  return wrapper

class Foo(object):
  def __init__(self, arg1):
    self.var1 = arg1

  @decorator
  def method1(self):
    print "method1"

foo = Foo("abc")
foo.method1()

If you want to have more generic decorator, it's better idea to declare a callable class:

class decorator:
  def __init__(self,varname):
      self.varname = varname
  def __call__(self,function):
    varname=self.varname
    def wrapper(self,*args,**kwargs):
      print "Doing something with self.%s==%s" % (varname,getattr(self,varname))
      return function(self,*args,**kwargs)
    return wrapper

Using:

  @decorator("var1")

Upvotes: 9

balpha
balpha

Reputation: 50908

It's not going to work; the decorator is called during class creation time, which is long before an instance is created (if that ever happens). So if your "decorator" needs the instance, you have to do the "decorating" at instantiation time:

def get_decorator(arg1):
    def my_decorator(function):
        print "get_decorator argument: %s" % arg1
        return function
    return my_decorator

class Foo(object):
    def __init__(self, arg1):
        self.var1 = arg1
        self.method1 = get_decorator(self.var1)(self.method1)

    def method1(self):
        print "method1"

foo = Foo("abc")
foo.method1()

Note that I changed the function names according to their meanings; the actual "decorator", i.e. the function that (potentially) modifies the method, is wrapper in your case, not decorator1.

Upvotes: 11

Related Questions