sdk sdk - 2 months ago 7
Python Question

Decorators in class methods: compatibility with 'getattr'

I need to make wrappers for class methods, to be executed before and/or after the call of a specific method.

Here is a minimal example:

class MyClass:

def call(self, name):
print "Executing function:", name
getattr(self, name)()

def my_decorator(some_function):
def wrapper():
print("Before we call the function.")
some_function()
print("After we call the function.")
return wrapper

@my_decorator
def my_function(self):
print "My function is called here."


engine = MyClass()
engine.call('my_function')


This gives me an error at the line
getattr(self, name)()
:


TypeError: 'NoneType' object is not callable


If I comment out the decorator before the class method, it works perfectly:

class MyClass:

def call(self, name):
print "Executing function:", name
getattr(self, name)()

def my_decorator(some_function):
def wrapper():
print("Before we call the function.")
some_function()
print("After we call the function.")
return wrapper

# @my_decorator
def my_function(self):
print "My function is called here."


engine = MyClass()
engine.call('my_function')


The output is:


Executing function: my_function

My function is called here.


The decorator itself is identical to textbook examples. It looks like something goes wrong at a low level when calling a decorated method in Python with
getattr
.

Do you have any ideas on how to fix this code?

Answer

This has nothing to do with getattr(). You get the exact same error when you try to call my_function() directly:

>>> engine.my_function()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object is not callable

You have 2 problems:

  • Your decorator never returns the wrapper, so None is returned instead. This return value replaces my_function and is the direct cause of your error; MyClass.my_function is set to None:

    >>> MyClass.my_function is None
    True
    
  • Your wrapper takes no arguments, including self. You'll need this for it to work once you do return it properly.

The first problem is fixed by un-indenting the return wrapper line; it is currently part of the wrapper function itself, and should be part of my_decorator instead:

def my_decorator(some_function):
    def wrapper(self):
        print("Before we call the function.")
        # some_function is no longer bound, so pass in `self` explicitly
        some_function(self)
        print("After we call the function.")
    # return the replacement function
    return wrapper
Comments