Ideas proposed here are excelent, but have some disadvantages:
inspect.getouterframes
and args[0].__class__.__name__
are not suitable for plain functions and static-methods.
__get__
must be in a class, that is rejected by @wraps
.
@wraps
itself should be hiding traces better.
So, I've combined some ideas from this page, links, docs and own head, and found a solution, that lacks all three disadvantages above.
Full module with self-tests can be downloaded from direct link: http://denis.ryzhkov.org/soft/python_lib/method_decorator.py
And here is just decorator class with docstring:
class method_decorator( object ):
'''
Abstract decorator that knows details about method, e.g. class where method is bound, that is None if method is plain function.
Also hides decorator traces by answering to system attributes more correctly than functools.wraps() does.
Tested with: instance-methods, class-methods, static-methods, and plain functions.
Inherit and override any behaviour.
Usage:
class my_dec( method_decorator ):
def __call__( self, *args, **kwargs ):
print( 'Calling %(method_type)s %(method_name)s from instance %(instance)s class %(class_name)s from module %(module_name)s with args %(args)s and kwargs %(kwargs)s.' % dict(
method_type=self.method_type,
method_name=self.__name__,
instance=self.obj,
class_name=( self.cls.__name__ if self.cls else None ),
module_name=self.__module__,
args=args,
kwargs=kwargs,
))
return method_decorator.__call__( self, *args, **kwargs )
@my_dec
def func_or_method( ...
'''
def __init__( self, func, obj=None, cls=None, method_type='function' ): # defaults are ok for plain functions, and will be changed by __get__ for methods
self.func, self.obj, self.cls, self.method_type = func, obj, cls, method_type
def __get__( self, obj=None, cls=None ): # is called as method: Cls.func or obj.func
if self.obj == obj and self.cls == cls: return self # use same instance, if it was already switched by previous __get__
if isinstance( self.func, staticmethod ): method_type = 'staticmethod' # later __get__ unwraps staticmethod object to plain function
elif isinstance( self.func, classmethod ): method_type = 'classmethod' # later Cls.classm is same as Cls.instancem( obj,..
else: method_type = 'instancemethod' # can't rely on obj in call like Cls.instancem( obj,..
return object.__getattribute__( self, '__class__' )( # use specialized decorator instance, don't change current instance attributes - it leads to conflicts
self.func.__get__( obj, cls ), obj, cls, method_type ) # use func's method version, [un]bound to [None-able] obj and cls.
def __call__( self, *args, **kwargs ):
return self.func( *args, **kwargs )
def __getattribute__( self, attr_name ): # hiding traces
if attr_name in ( '__init__', '__get__', '__call__', '__getattribute__', 'func', 'obj', 'cls', 'method_type' ): # our known names, our '__class__' is used only once with explicit object.__getattribute__
return object.__getattribute__( self, attr_name ) # stopping recursion
# all other attr_names, that can be auto-defined by system in self, and all others, are searched in self.func, e.g.: __module__, __class__, __name__, __doc__, im_*, func_*, etc.
return getattr( self.func, attr_name ) # raises AttributeError if name not found in self.func
def __repr__( self ): # ignores __getattribute__
return self.func.__repr__()