Domanda

I'm currently trying to implement a class decorator that can add multiple methods depending on the arguments. For example:

@decorator('my_func')
class Screen:
    ...

will add the method 'my_func' (the body of the function is defined in the decorator) to the test class.

@decorator('my_func', 'my_func2')
class Screen:
   ...

will add the methods 'my_func' and 'my_func2'. So far I've managed to do the following:

def decorator(*args):
    def wrapper(cls):
        if not issubclass(cls, Screen):
            raise ValueError("You can't apply this decorator "
                             "on this class.")

        for arg in args:
            def clicker(self):
                return "Hey !" + arg
            setattr(cls, arg, clicker)
        return cls
    return wrapper

The issue here is that all methods defined 'my_func' or 'my_func2' points to the same code, the last one defined (in my example the code for 'my_func2'). For instance:

>>> Screen().my_func()
"Hey ! my_func2"
>>> Screen().my_func2()
"Hey ! my_func2"

I've tried with a lot of tweaks such as copying the method with FunctionType, bounding with with MethodType, the result remains always the same.

But this code has the expected behaviour when the decorators are chained:

@decorator('my_func')
@decorator('my_func2')
class Test:
    ...

My goal here is to use this decorator without resorting to chaining which can be lame when you have a lot of parameter to pass

Any ideas are welcome !

È stato utile?

Soluzione

Thanks to the comments provided, I was able to fix my issue. The problem, as described by @Martijn Pieters, was coming from the fact that I was creating a closure for each function while disregarding the scope of 'arg', hence it was the iteration of the loop that was binding the value. By passing 'arg' as an argument, I can now pass my parameter in the inner scope of my closure:

def decorator(*args):
    def wrapper(cls):
        if not issubclass(cls, Screen):
            raise ValueError("You can't apply this decorator "
                             "on this class.")

        for arg in args:
            def clicker(self, marg=arg):
                return "Hey !" + marg
            setattr(cls, arg, clicker)
        return cls
    return wrapper
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top