Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
222 lines (172 sloc) 8 KB
'''
Python decorator that knows the class the decorated method is bound to.
Please see full description here:
https://github.com/denis-ryzhkov/method_decorator/blob/master/README.md
method_decorator version 0.1.3
Copyright (C) 2013 by Denis Ryzhkov <denisr@denisr.com>
MIT License, see http://opensource.org/licenses/MIT
'''
#### method_decorator
class method_decorator(object):
def __init__(self, func, obj=None, cls=None, method_type='function'):
# These defaults are OK for plain functions
# and will be changed by __get__() for methods once a method is dot-referenced.
self.func, self.obj, self.cls, self.method_type = func, obj, cls, method_type
def __get__(self, obj=None, cls=None):
# It is executed when decorated func is referenced as a method: cls.func or obj.func.
if self.obj == obj and self.cls == cls:
return self # Use the same instance that is already processed by previous call to this __get__().
method_type = (
'staticmethod' if isinstance(self.func, staticmethod) else
'classmethod' if isinstance(self.func, classmethod) else
'instancemethod'
# No branch for plain function - correct method_type for it is already set in __init__() defaults.
)
return object.__getattribute__(self, '__class__')( # Use specialized method_decorator (or descendant) instance, don't change current instance attributes - it leads to conflicts.
self.func.__get__(obj, cls), obj, cls, method_type) # Use bound or unbound method with this underlying func.
def __call__(self, *args, **kwargs):
return self.func(*args, **kwargs)
def __getattribute__(self, attr_name): # Hiding traces of decoration.
if attr_name in ('__init__', '__get__', '__call__', '__getattribute__', 'func', 'obj', 'cls', 'method_type'): # Our known names. '__class__' is not included because is used only with explicit object.__getattribute__().
return object.__getattribute__(self, attr_name) # Stopping recursion.
# All other attr_names, including auto-defined by system in self, are searched in decorated self.func, e.g.: __module__, __class__, __name__, __doc__, im_*, func_*, etc.
return getattr(self.func, attr_name) # Raises correct AttributeError if name is not found in decorated self.func.
def __repr__(self): # Special case: __repr__ ignores __getattribute__.
return self.func.__repr__()
#### test
def test():
#### my_decorator
class my_decorator(method_decorator):
def __call__(self, *args, **kwargs):
print('Calling {method_type} {method_name} from instance {instance} of class {class_name} from module {module_name} with args {args} and kwargs {kwargs}.'.format(
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)
#### MyClass
class MyClass(object):
@my_decorator
def my_instance_method(self, arg, kwarg='default'):
'''my_instance_method doc.'''
return dict(arg=arg, kwarg=kwarg)
@my_decorator
@classmethod
def my_class_method(cls, arg):
return arg
@my_decorator
@staticmethod
def my_static_method(arg):
return arg
my_class_module_name = MyClass.__module__
my_instance = MyClass()
#### my_plain_function
@my_decorator
def my_plain_function(arg):
return arg
#### instancemethod
result = my_instance.my_instance_method
assert result.method_type == 'instancemethod'
assert result.__name__ == 'my_instance_method'
assert result.obj == my_instance
assert result.cls == MyClass
assert result.cls.__name__ == 'MyClass'
assert result.__module__ == my_class_module_name
assert result.__doc__ == 'my_instance_method doc.'
assert result.im_self == my_instance
assert result.im_class == MyClass
assert repr(type(result.im_func)) == "<type 'function'>"
assert result.func_defaults == ('default', )
try:
result.invalid
assert False, 'Expected AttributeError'
except AttributeError:
pass
result = my_instance.my_instance_method('bound', kwarg='kwarg')
assert result == dict(arg='bound', kwarg='kwarg')
result = my_instance.my_instance_method('bound')
assert result == dict(arg='bound', kwarg='default')
result = MyClass.my_instance_method
assert result.method_type == 'instancemethod'
assert result.__name__ == 'my_instance_method'
assert result.obj == None
assert result.cls == MyClass
assert result.cls.__name__ == 'MyClass'
assert result.__module__ == my_class_module_name
assert result.__doc__ == 'my_instance_method doc.'
assert result.im_self == None
assert result.im_class == MyClass
assert repr(type(result.im_func)) == "<type 'function'>"
assert result.func_defaults == ('default', )
result = MyClass.my_instance_method(MyClass(), 'unbound')
assert result['arg'] == 'unbound'
#### classmethod
result = MyClass.my_class_method
assert result.method_type == 'classmethod'
assert result.__name__ == 'my_class_method'
assert result.obj == None
assert result.cls == MyClass
assert result.cls.__name__ == 'MyClass'
assert result.__module__ == my_class_module_name
assert result.im_self == MyClass
assert result.im_class == type
assert repr(type(result.im_func)) == "<type 'function'>", type(result.im_func)
result = MyClass.my_class_method('MyClass.my_class_method')
assert result == 'MyClass.my_class_method'
result = my_instance.my_class_method
assert result.method_type == 'classmethod'
assert result.__name__ == 'my_class_method'
assert result.obj == my_instance
assert result.cls == MyClass
assert result.cls.__name__ == 'MyClass'
assert result.__module__ == my_class_module_name
assert result.im_self == MyClass
assert result.im_class == type
assert repr(type(result.im_func)) == "<type 'function'>", type(result.im_func)
result = my_instance.my_class_method('my_instance.my_class_method')
assert result == 'my_instance.my_class_method'
#### staticmethod
result = MyClass.my_static_method
assert result.method_type == 'staticmethod'
assert result.__name__ == 'my_static_method'
assert result.obj == None
assert result.cls == MyClass
assert result.cls.__name__ == 'MyClass'
assert result.__module__ == my_class_module_name
assert not hasattr(result, 'im_self')
assert not hasattr(result, 'im_class')
assert not hasattr(result, 'im_func')
result = MyClass.my_static_method('MyClass.my_static_method')
assert result == 'MyClass.my_static_method'
result = my_instance.my_static_method
assert result.method_type == 'staticmethod'
assert result.__name__ == 'my_static_method'
assert result.obj == my_instance
assert result.cls == MyClass
assert result.cls.__name__ == 'MyClass'
assert result.__module__ == my_class_module_name
assert not hasattr(result, 'im_self')
assert not hasattr(result, 'im_class')
assert not hasattr(result, 'im_func')
result = my_instance.my_static_method('my_instance.my_static_method')
assert result == 'my_instance.my_static_method'
#### plain function
result = my_plain_function
assert result.method_type == 'function'
assert result.__name__ == 'my_plain_function'
assert result.obj == None
assert result.cls == None
assert result.__module__ == my_class_module_name
assert not hasattr(result, 'im_self')
assert not hasattr(result, 'im_class')
assert not hasattr(result, 'im_func')
result = my_plain_function('my_plain_function')
assert result == 'my_plain_function'
#### OK
print('OK')
if __name__ == '__main__':
test()