How To Detect Is Decorator Has Been Applied To Method Or Function?
Solution 1:
You can solve this problem using descriptor protocol. By returning non-data descriptor from decorator you get to implement __get__
where you can save the method's instance/class.
Another (simpler) way would be to detect instance/class late, in decorator-made wrapper which may have self
or cls
as first of *args
. This improves "inspectability" of decorated function, as it's still a plain function and not a custom non-data-desctiptor/function-object.
Problem we have to solve is that we cannot hook into or before the method binding:
Note that the transformation from function object to (unbound or bound) method object happens each time the attribute is retrieved from the class or instance.
In other words: when our wrapper runs, its descriptor protocol, namely __get__
method-wrapper of function, has already bound function with class/instance and resulting method is already being executed. We're left with args/kwargs and no straightforwardly accessible class-related info in current stack frame.
Let's start with solving class/staticmethod special cases and implementing wrapper as simple printer:
def decorated(fun):
desc = next((desc for desc in (staticmethod, classmethod)
if isinstance(fun, desc)), None)if desc:
fun = fun.__func__@wraps(fun)
def wrap(*args, **kwargs):
cls, nonselfargs = _declassify(fun, args)
clsname = cls.__name__ if cls else None
print('class: %-10s func: %-15s args: %-10s kwargs: %-10s' %
(clsname, fun.__name__, nonselfargs, kwargs))
wrap.original = funif desc:
wrap = desc(wrap)
return wrap
Here comes the tricky part - if this was a method/classmethod call, first of args must be instance/class respectively. If so, we can get the very method we execute from this arg. If so, wrapper which we implemented above will be inside as __func__
. If so, original
member will be in our wrapper. If it is identical to fun
from closure, we're home and can slice instance/class safely from remaining args.
def _declassify(fun, args):if len(args):
met = getattr(args[0], fun.__name__, None)
if met:
wrap = getattr(met, '__func__', None)
if getattr(wrap, 'original', None) isfun:
maybe_cls = args[0]
cls = maybe_cls if isclass(maybe_cls) else maybe_cls.__class__
return cls, args[1:]
return None, args
Let's see if this works with different variants of functions/methods:
@decorated
def simplefun():
pass
class Class(object):
@decorated
def __init__(self):
pass
@decorated
def method(self, a, b):
pass
@decorated@staticmethod
def staticmethod(a1, a2=None):
pass
@decorated@classmethod
def classmethod(cls):
pass
Let's see if this actually runs:
simplefun()
instance = Class()
instance.method(1, 2)
instance.staticmethod(a1=3)
instance.classmethod()
Class.staticmethod(a1=3)
Class.classmethod()
output:
$ python Example5.py
class: None func: simplefunargs: () kwargs: {}
class: Class func: __init__args: () kwargs: {}
class: Class func: methodargs: (1, 2) kwargs: {}
class: None func: staticmethodargs: () kwargs: {'a1': 3}
class: Class func: classmethodargs: () kwargs: {}
class: None func: staticmethodargs: () kwargs: {'a1': 3}
class: Class func: classmethodargs: () kwargs: {}
Solution 2:
You can use inspect.getargspec
:
import inspect
def _is_method(func):
spec = inspect.getargspec(func)return spec.args and spec.args[0] == 'self'
Example usage:
>>>defdummy_deco(f):...print('{} is method? {}'.format(f.__name__, _is_method(f)))...return f...>>>@dummy_deco...defadd(a, b):...return a + b...
add is method? False
>>>classA:... @dummy_deco...defmeth(self, a, b):...return a + b...
meth is method? True
NOTE This code depend on the name of the first argument. If the name is not self
it will treat it as non-instance-method even though it is.
Solution 3:
Thanks to this SO answer: Using the same decorator (with arguments) with functions and methods
I came to this solution witch works for me flawlessly:
defproofOfConcept():
defwrapper(func):
classMethodDecoratorAdapter(object):
def__init__(self, func):
self.func = func
self.is_method = Falsedef__get__(self, instance, owner):
ifnot self.is_method:
self.is_method = True
self.instance = instance
return self
def__call__(self, *args, **kwargs):
# Decorator real logic goes hereif self.is_method:
return self.func(self.instance, *args, **kwargs)
else:
return self.func(*args, **kwargs)
return wraps(func)(MethodDecoratorAdapter(func))
return wrapper
NOTE This is not thread safe, to have a thread safe method one must return a callable object from __get__
that will have scope tied to instance
Solution 4:
Solution for python3:
import inspect
def_is_method(func):
spec = inspect.signature(func)
iflen(spec.parameters) > 0:
iflist(spec.parameters.keys())[0] == 'self':
returnTruereturnFalse
Solution 5:
import functools
import inspect
defmydec():
defdecorator(func):
@functools.wraps(func)defpickled_func(*args, **kwargs):
is_method = Falseiflen(args) > 0:
method = getattr(args[0], func.__name__, False)
if method:
wrapped = getattr(method, "__wrapped__", False)
if wrapped and wrapped == func:
print("Used mydec to a method")
is_method = Trueif is_method isFalse:
print("Used mydec not to a method.")
result = func(*args, **kwargs)
return result
return decorator
Check whether the __wrapped__
variable is the same function as decorated.
Post a Comment for "How To Detect Is Decorator Has Been Applied To Method Or Function?"