2

I need to decorate a object's method. It needs to be at runtime because the decorators applied on the object depends on the arguments that the user gave when calling the program (arguments supplied with argv), so a same object could be decorated 3 times, 2 times, or not be decorated at all.

Here is some context, the program is a puzzle solver, the main behavior is to find a solution for the puzzle automatically, by automatically I mean without user intervention. And here is where the decoration gets to play, one of the things I want to is draw a graph of what happened during the execution, but I want to do so only when the flag --draw-graph is used.

Here is what I've tried:

class GraphDecorator(object):
    def __init__(self, wrappee):
        self.wrappee = wrappee
    def method(self):
        # do my stuff here
        self.wrappee.method()
        # do more of stuff here
    def __getattr__(self,attr):
        return getattr(self.wrappee,attr)

And why it did NOT work: It did not work because of the way I built the application, when a method that did not exist in my Decorator class was called it felt back to the implementation of the decorated class, the problem is that the application always started invoking the method run that did not need to be decorated, so the undecorated fall back was used and from inside the undecorated form it always called undecorated methods, what I needed was to replace the method from the object, not to proxy it:

# method responsible to replace the undecorated form by the decorated one
def graphDecorator(obj):
    old_method = obj.method

    def method(self):
        # do my stuff here
        old_method()
        # do more of my stuff

    setattr(obj,'method',method) # replace with the decorated form

And here is my problem, the decorated form does not receive self when it is called resulting on a TypeError because of the wrong number of arguments.

agf
  • 171,228
  • 44
  • 289
  • 238
Augusto Hack
  • 2,032
  • 18
  • 35
  • 1
    possible duplicate of [Implementing the decorator pattern in Python](http://stackoverflow.com/questions/3118929/implementing-the-decorator-pattern-in-python) – Björn Pollex May 10 '11 at 13:25
  • Space_C0wb0y - I have a problem with `__getattr__`, the class A is the class decorated by the class B, A has a method run() that calls method do(), the class B decorated the method do(), but because I use recursion within run() and I start it from class A method's run(), it calls it's own version of do(), and not the decorated one. – Augusto Hack May 10 '11 at 13:41
  • Your question could do with some clarifications, but if I read it correctly, you want to "hack" into some already written code in another module, so that it instantiates a modified object instead of the one it usually uses. If so, [this answer](http://stackoverflow.com/questions/3464061/cast-base-class-to-derived-class-python-or-more-pythonic-way-of-extending-classe/4714744#4714744) will tell you a way to do that. – Lauritz V. Thaulow May 10 '11 at 15:03

4 Answers4

2

The problem was that I couldn't use func(self) as a method. The reason is that setattr() method does not bound the function, and the function acts like it a static method - not a class method -, thanks to the introspective nature of python I've able to come up with this solution:

def decorator(obj):
    old_func = obj.func # can't call 'by name' because of recursion

    def decorated_func(self):
        # do my stuff here
        old_func() # does not need pass obj
        # do some othere stuff here

    # here is the magic, this get the type of a 'normal method' of a class
    method = type(obj.func)

    # this bounds the method to the object, so self is passed by default 
    obj.func = method(decorated_func, obj)

I think this is the best way to decorate a object's method at runtime, though it would be nice to find a way to call method() directly, without the line method = type(obj.func)

Augusto Hack
  • 2,032
  • 18
  • 35
0

You might want to use __getattribute__ instead of __getattr__ (the latter being only called if "standard" lookup fails):

class GraphDecorator(object):
    def __init__(self, wrappee):
        self.__wrappee = wrappee

    def method(self):
        # do my stuff here
        self.wrappe.method()
        # do more of stuff here

    def __getattribute__(self, name):
        try:
            wrappee = object.__getattribute__(self, "_GraphDecorator__wrappee")
            return getattr(wrappee, name)
        except AttributeError:
            return object.__getattribute__(self, name)
filmor
  • 30,840
  • 6
  • 50
  • 48
0

I need to decorate a object's method. It needs to be at runtime because the decorators applied on the object depends on the arguments that the user gave when calling the program (arguments supplied with argv), so a same object could be decorated 3 times, 2 times, or not be decorated at all.

The above is unfortunately incorrect, and what you are trying to do is unnecessary. You can do this at runtime like so. Example:

import sys
args = sys.argv[1:]

class MyClass(object):
    pass

if args[0]=='--decorateWithFoo':
    MyClass = decoratorFoo(MyClass)
if args[1]=='--decorateWithBar'
    MyClass = decoratorBar(MyClass)

The syntax:

@deco
define something

Is the same thing as:

define something
something = deco(something)

You could also make a decorator factory @makeDecorator(command_line_arguments)

ninjagecko
  • 88,546
  • 24
  • 137
  • 145
  • I don't understand what is incorrect, that is what I did. I do know that the expression `@deco` is a syntax sugar for `func = deco(func)`, but it does not do the same for **methods**, if you decorate a method while you are defining the class it will create a `method`, try this: `type(class().method)` after you have defined the class and decorated the method. If you decorated calling the function afterwards, it creates a `function`, try: `type(deco(class().method))`. – Augusto Hack May 11 '11 at 13:31
0

"It needs to be at runtime because the decorators applied on the object depends on the arguments that the user gave when calling the program"

The don't use decorators. Decorators are only syntactical support for wrappers, you can just as well use normal function/method calls instead.

Lennart Regebro
  • 167,292
  • 41
  • 224
  • 251