Get method wrapped in a descriptor with getattr

2.4k Views Asked by At

I have the following descriptor, which saves the configuration inside my class after a method which is annotated with @saveconfig is called:

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

    def __get__(self, instance, owner):
        def wrapper(*args):
            self.f(instance, *args)
            instance.cfg.write()
            instance.paramcfg.write()
        return wrapper

It's used like this:

class pbtools():
    @saveconfig
    def getip(self, ip):
        (...)

It works fine. Now i want to get the decorated method by using getattr. But since the method is wrapped by the descriptor, i only get wrapper as a result:

pbt = pbtools()
# results in "<function wrapper at 0x23cced8>:"
method = getattr(pbt, "getip")

How can i access the wrapped method getip with getattr, to be able to call it by it's name? (of course i cannot access the method directly otherwise i would not have to do that).

Additional explanation:

The script is called from commandline. I have to map a command line parameter (string) to a method with the same name to invoke it. Furthermore, i have to map any additional parameters from the comamndline to parameters of the method, like this:

pbtools getip 192.168.178.123 #-> calls getip with parameter 192.168.178.123

Since i cannot get the original method, i don't know how many parameters it has to map them. I have several methods that are decorated like that, because i want to move the cross cutting concern of saving the config out of the methods in the pbtools-class.

2

There are 2 best solutions below

1
On BEST ANSWER

I'm still not 100% sure I fully understand your problem. You say that "Since i cannot get the original method, i don't know how many parameters it has to map them", but you don't need access to the original method to call by variable number of arguments (since the decorator has *args). You can do something like this:

import sys
cmd = sys.argv[1]
params = sys.argv[2:]

pbt = pbtools()

func = getattr(pbt, cmd)
func(*params)

You can also simplify your decorator a bit. It doesn't really need the __get__ mechanism.

import functools

def saveconfig(f):
    @functools.wraps(f)
    def wrapper(self, *args):
        f(self, *args)
        self.cfg.write()
        self.paramcfg.write()
    return wrapper

The functools.wraps is a helper decorator that makes the wrapper mimic the original function (i.e. it copies the function name, docstring and stuff like that). It will make debugging easier, since you will know where exceptions are originating from etc.

1
On

First of all sorry about the comment my mistake, now for your last code will not work as you which (your method is not decorated anymore) because to understand decorator you have to see this :

class pbtools():
    @saveconfig
    def getip():
        (...)

is equivalent to:

class pbtools():

    def getip():
        (...)

    getip = saveconfig(getip)

and in your latest case saveconfig return self.f which is in our code equal to getip so in this case this code:

getip = saveconfig(getip)

is equivalent to :

 getip = getip

so basically it don't do nothing.

A work around can be by saving the wrapped function in the wrapping one like this:

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

    def __get__(self, instance, owner):
        def wrapper(*args):
            self.f(instance, *args)
            instance.cfg.write()
            instance.paramcfg.write()

        wrapper.func = self.f    # Create a new attribute and assign it to the wrapped func.
        return wrapper

and now you can :

class pbtools():
    @saveconfig
    def getip():
        print "hi"

pbt = pbtools()
method = getattr(pbt, "getip")
print method.func
# <function getip at 0x2363410>
method.func()
# hi

Hope this can help :)