Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python: can a decorator determine if a function is being defined inside a class?

Tags:

python

inspect

I'm writing a decorator, and for various annoying reasons[0] it would be expedient to check if the function it is wrapping is being defined stand-alone or as part of a class (and further which classes that new class is subclassing).

For example:

def my_decorator(f):
    defined_in_class = ??
    print "%r: %s" %(f, defined_in_class)

@my_decorator
def foo(): pass

class Bar(object):
    @my_decorator
    def bar(self): pass

Should print:

<function foo …>: False
<function bar …>: True

Also, please note:

  • At the point decorators are applied the function will still be a function, not an unbound method, so testing for instance/unbound method (using typeof or inspect) will not work.
  • Please only offer suggestions that solve this problem — I'm aware that there are many similar ways to accomplish this end (ex, using a class decorator), but I would like them to happen at decoration time, not later.

[0]: specifically, I'm writing a decorator that will make it easy to do parameterized testing with nose. However, nose will not run test generators on subclasses of unittest.TestCase, so I would like my decorator to be able to determine if it's being used inside a subclass of TestCase and fail with an appropriate error. The obvious solution - using isinstance(self, TestCase) before calling the wrapped function doesn't work, because the wrapped function needs to be a generator, which doesn't get executed at all.

like image 438
David Wolever Avatar asked Jan 09 '12 18:01

David Wolever


People also ask

Which of the following is true about decorators in Python?

Decorators can be chained A Decorator function is used only to format the output of another function dec keyword is used for decorating a function Decorators always return None” Code Answer.

When a function is defined inside a class in Python?

When a function is defined inside a class, we call it a Method.

Can we use decorators on class in Python?

In Python, decorators can be either functions or classes. In both cases, decorating adds functionality to existing functions. When we decorate a function with a class, that function becomes an instance of the class. We can add functionality to the function by defining methods in the decorating class.

How are decorators used in class?

To decorate a method in a class, first use the '@' symbol followed by the name of the decorator function. A decorator is simply a function that takes a function as an argument and returns yet another function. Here, when we decorate, multiply_together with integer_check, the integer function gets called.


2 Answers

Take a look at the output of inspect.stack() when you wrap a method. When your decorator's execution is underway, the current stack frame is the function call to your decorator; the next stack frame down is the @ wrapping action that is being applied to the new method; and the third frame will be the class definition itself, which merits a separate stack frame because the class definition is its own namespace (that is wrapped up to create a class when it is done executing).

I suggest, therefore:

defined_in_class = (len(frames) > 2 and
                    frames[2][4][0].strip().startswith('class '))

If all of those crazy indexes look unmaintainable, then you can be more explicit by taking the frame apart piece by piece, like this:

import inspect
frames = inspect.stack()
defined_in_class = False
if len(frames) > 2:
    maybe_class_frame = frames[2]
    statement_list = maybe_class_frame[4]
    first_statment = statement_list[0]
    if first_statment.strip().startswith('class '):
        defined_in_class = True

Note that I do not see any way to ask Python about the class name or inheritance hierarchy at the moment your wrapper runs; that point is "too early" in the processing steps, since the class creation is not yet finished. Either parse the line that begins with class yourself and then look in that frame's globals to find the superclass, or else poke around the frames[1] code object to see what you can learn — it appears that the class name winds up being frames[1][0].f_code.co_name in the above code, but I cannot find any way to learn what superclasses will be attached when the class creation finishes up.

like image 117
Brandon Rhodes Avatar answered Oct 16 '22 22:10

Brandon Rhodes


A little late to the party here, but this has proven to be a reliable means of determining if a decorator is being used on a function defined in a class:

frames = inspect.stack()

className = None
for frame in frames[1:]:
    if frame[3] == "<module>":
        # At module level, go no further
        break
    elif '__module__' in frame[0].f_code.co_names:
        className = frame[0].f_code.co_name
        break

The advantage of this method over the accepted answer is that it works with e.g. py2exe.

like image 45
Walt W Avatar answered Oct 16 '22 21:10

Walt W