Wolkenarchitekt
Wolkenarchitekt

Reputation: 21238

Inside a decorator-class, access instance of the class which contains the decorated method

I have the following decorator, which saves a configuration file after a method decorated with @saveconfig is called:

class saveconfig(object):
    def __init__(self, f):
        self.f = f

    def __call__(self, *args):
        self.f(object, *args)
        # Here i want to access "cfg" defined in pbtools
        print "Saving configuration"

I'm using this decorator inside the following class. After the method createkvm is called, the configuration object self.cfg should be saved inside the decorator:

class pbtools()
    def __init__(self):
        self.configfile = open("pbt.properties", 'r+')
        # This variable should be available inside my decorator
        self.cfg = ConfigObj(infile = self.configfile)

    @saveconfig
    def createkvm(self):
        print "creating kvm"

My problem is that i need to access the object variable self.cfg inside the decorator saveconfig. A first naive approach was to add a parameter to the decorator which holds the object, like @saveconfig(self), but this doesn't work.

How can I access object variables of the method host inside the decorator? Do i have to define the decorator inside the same class to get access?

Upvotes: 8

Views: 2788

Answers (3)

Jochen Ritzel
Jochen Ritzel

Reputation: 107608

You can also use a simple function for what you want:

def saveconfig(f):
    # this method replaces the decorated, so `self` will be the pbtools instance
    def wrapped(self, *args):
        f(self, *args)
        # Here i want to access "cfg" defined in pbtools
        print "Saving configuration", self.cfg
    return wrapped

If saveconfig must be a class then you need Sven's solution.

Upvotes: 0

user395760
user395760

Reputation:

You are passing object as self to the decorated method. The thing is, you can't easily get self because Python sees the decorated method, which is now an objects, and doesn't consider it a method (that should be passed self when called - or, more generally, that should work as a property returning a bound method). You can work around this, as pointed out by @Sven Marnach.

However, you could easily rewrite this decorator without a class, using a closure (is a bit shorter and also solves the above problem):

def saveconfig(f):
    @functools.wraps(f) # to preserve name, docstring, etc.
    def wrapper(*args, **kwargs): # **kwargs for compability with functions that use them
        f(*args, **kwargs)
        # save config
    return wrapper

Other notes:

  • Terminology mixup: There is no class variable in the example. A class variable would be x = ... indented as far as the method definitions and be shared between all instances (specifically, it would be an attribute of the object that is pbtools) - everything on self is an instance attribute.
  • At class definition time (when you define methods, apply decorators, etc.) there is no self!

Upvotes: 4

Sven Marnach
Sven Marnach

Reputation: 601599

You have to make your decorator class behave as a descriptor to be able to access the instance:

class saveconfig(object):
    def __init__(self, f):
        self.f = f

    def __get__(self, instance, owner):
        def wrapper(*args):
            print "Saving configuration"
            print instance.cfg
            return self.f(instance, *args)
        return wrapper

Your code passes object as first parameter to self.f(), where it should pass the pbtools instance.

Upvotes: 11

Related Questions