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

巧了我就是萌 提交于 2019-12-03 05:33:44
Brandon Rhodes

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.

Some hacky solution that I've got:

import inspect

def my_decorator(f):
    args = inspect.getargspec(f).args
    defined_in_class = bool(args and args[0] == 'self')
    print "%r: %s" %(f, defined_in_class)

But it relays on the presence of self argument in function.

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.

You could check if the decorator itself is being called at the module level or nested within something else.

defined_in_class = inspect.currentframe().f_back.f_code.co_name != "<module>"

you can use the package wrapt to check for
- instance/class methods
- classes
- freestanding functions/static methods:

See the project page of wrapt: https://pypi.org/project/wrapt/

I think the functions in the inspect module will do what you want, particularly isfunction and ismethod:

>>> import inspect
>>> def foo(): pass
... 
>>> inspect.isfunction(foo)
True
>>> inspect.ismethod(foo)
False
>>> class C(object):
...     def foo(self):
...             pass
... 
>>> inspect.isfunction(C.foo)
False
>>> inspect.ismethod(C.foo)
True
>>> inspect.isfunction(C().foo)
False
>>> inspect.ismethod(C().foo)
True

You can then follow the Types and Members table to access the function inside the bound or unbound method:

>>> C.foo.im_func
<function foo at 0x1062dfaa0>
>>> inspect.isfunction(C.foo.im_func)
True
>>> inspect.ismethod(C.foo.im_func)
False
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!