Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python: Anything wrong with dynamically assigning instance methods as instance attributes

Tags:

python

oop

I came up with the following code to decorate instance methods using a decorator that requires the instance itself as an argument:

from functools import wraps

def logging_decorator(tricky_instance):
    def wrapper(fn):
        @wraps(fn)
        def wrapped(*a, **kw):
            if tricky_instance.log:
                print("Calling %s.." % fn.__name__)
            return fn(*a, **kw)
        return wrapped
    return wrapper     

class Tricky(object):
    def __init__(self, log):
        self.log = log
        self.say_hi = logging_decorator(self)(self.say_hi)

    def say_hi(self):
       print("Hello, world!")


i1 = Tricky(log=True)
i2 = Tricky(log=False)

i1.say_hi()
i2.say_hi()

This seems to work great, but I fear that I may have overlooked some unintended side effects of this trick. Am I about to shoot myself in the foot, or is this safe?

Note that I don't actually want to use this for logging, it's just the shortest meaningful example I could come up with.

like image 251
Nikratio Avatar asked Aug 18 '11 00:08

Nikratio


2 Answers

It's not really clear to me why you would ever want to do this. If you want to assign a new method type dynamically use types:

import types

class Tricky(object):
    def __init__(self):
        def method(self):
            print('Hello')
        self.method = types.MethodType(method, self)

If you want to do something with the instance, do it in the __init__ method. If you just want access to the method's instance inside the decorator, you can use the im_self attribute:

def decorator(tricky_instance):
    def wrapper(meth):
        print(meth.im_self == tricky_instance)
        return meth
    return wrapper

Personally, I think this is veering into Maybe-I-Shouldn't-Use-Decorators land.

like image 65
zeekay Avatar answered Oct 04 '22 07:10

zeekay


I think I was trying to be needlessly smart. There seems to be an embarrassingly simpler solution:

from functools import wraps

def logging_decorator(fn):
    @wraps(fn)
    def wrapped(self, *a, **kw):
        if self.log:
            print("Calling %s.." % fn.__name__)
        return fn(self, *a, **kw)
    return wrapped

class Tricky(object):
    def __init__(self, log):
        self.log = log

    @logging_decorator
    def say_hi(self):
       print("Hello, world!")

i1 = Tricky(log=True)
i2 = Tricky(log=False)

i1.say_hi()
i2.say_hi()
like image 24
Nikratio Avatar answered Oct 04 '22 08:10

Nikratio