Python decorators theory - python

I have question about clean thory in Python. When:
#decorator_func
def func(bla, alba):
pass
Is equivalent to:
def func(bla, alba):
pass
func = decorator_func(func)
So:
#decorator_func(aaa, bar)
def func(bla, alba):
pass
Is equvalent to...?

It's equivalent to:
def func(bla, alba):
pass
func = decorator_func(aaa, bar)(func)
Or:
def func(bla, alba):
pass
decorator = decorator_func(aaa, bar)
func = decorator(func)
So in your second example, decorator_func should be a callable that returns a callable.
Here's an example of such a construction:
class prepend_two_arguments:
def __init__(self, a, b):
self.a = a
self.b = b
def __call__(self, f):
def wrapped_function(*args, **kwargs):
return f(self.a, self.b, *args, **kwargs)
return wrapped_function
#prepend_two_arguments(1,2)
def f(a, b, c):
return a+b+c
print(f(3)) # 6
And another one, using only functions:
def add_to_result(x):
def decorator(fn):
def wrapped_function(*args, **kwargs):
return fn(*args, **kwargs)+x
return wrapped_function
return decorator
#add_to_result(3)
def my_func(a, b):
return a+b
print(my_func(1,2)) # 6

Here's an example of a decorator function that works using closures:
def print_string_before(string):
def decorator_fn(fn):
def wrapped_fn(*args, **kwargs):
print string
return fn(*args, **kwargs)
return wrapped_fn
return decorator_fn
Note that decorators can equally return the decorated function (or class), having modified it in some way (e.g. setting an attribute).

Related

Python decorators in classes with extra parameters

I read this
one answer
and this
another answer
but I'm not doing it right with parameters
class decoratortest:
def dec(func):
def wrapper(self,*args,**kwargs):
func(*args,**kwargs)
func(*args,**kwargs)
return wrapper
#dec
def printer(self,a):
print(a)
def dectest(self):
self.printer('hi')
x = decoratortest()
x.dectest()
I get the usual positional error argument. What's the right syntax so I can print hi twice?
For the future, this worked:
class decoratortest:
def dec(func):
def wrapper(self,*args,**kwargs):
func(self,*args,**kwargs)
func(self,*args,**kwargs)
return wrapper
#dec
def printer(self,a):
print(a)
def dectest(self):
self.printer('hi')
x = decoratortest()
x.dectest()
very tricky, you dont' type self in the decorator, but you do in the underlying wrapper and func items.
You have to pass self explicitly, since func is a reference to a regular function object, not the method object that self.printer produces (via the descriptor protocol):
def dec(func):
def wrapper(self, *args, **kwargs):
func(self, *args, **kwargs)
func(self, *args, **kwargs)
return wrapper
Or you can put your decorator outside of the class
def dec(func):
def wrapper(*args, **kwargs):
func(*args, **kwargs)
func(*args, **kwargs)
return wrapper
class decoratortest:
#dec
def printer(self, a):
print(a)
def dectest(self):
self.printer('hi')
x = decoratortest()
x.dectest()
Output:
hi
hi
class decoratortest:
def dec(func):
def wrapper(self,*args,**kwargs):
func(*args,**kwargs)
func(*args,**kwargs)
return wrapper
#dec
def printer(self,a='hi'):
print(a)
def dectest(self):
self.printer('hi')
x = decoratortest()
x.dectest()
Added a default value for decorator when argument is not specified and it worked.

Decorators as classes

Trying to rewrite a decorator as a Class isn't working as expected. My actual decorator is:
def check(callback):
def decorator(function):
def wrapper(*args, **kwargs):
result = function(*args, **kwargs)
cb_result = callback()
return result
return wrapper
return decorator
My approach to class format is
class Check(object):
def __init__(self, *args, **kwargs):
self._args = args
self._kwargs = kwargs
def __call__(self, *call_args, **call_kwargs):
function = call_args[0]
return self.__param__call__(function)
def __param__call__(self, function):
def wrapper(*args, **kwargs):
result = function(*args, **kwargs)
cb_result = callback()
return result
return wrapper
I expect to use the decorator as in:
#Check(callback=a_function_callback)
def my_function():
...
What is my mistake while rewriting it as a class, I'm also always trying to keep backwards compatibility (aka Python 2.7 compliance).
You should accept callback as a parameter in your Check class' __init__ method, so that your wrapper function can actually reference it as a callback function:
class Check(object):
def __init__(self, callback):
self.callback = callback
def __call__(self, func):
return self.__param__call__(func)
def __param__call__(self, func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
cb_result = self.callback()
return result
return wrapper

pythonic way of decorating classmethods

Given the following example:
class A:
def f(self, x):
return 2*x
I would like to write another method which uses f above but adds a constant, i.e.
class A:
def f(self, x):
return 2*x
def g(self, x):
return self.f(x) + 10
This would be one way. However, this smells very much like decorating! What would be the proper pythonic way to do this?
Extending answer of #Ajax1234, you may try to:
- parametrize your decorator,
- call your decorator explicitly (without #).
def add_val(_value):
def decorator(f):
def wrapper(cls, _x):
return f(cls, _x) + _value
return wrapper
return decorator
class A:
def f(self, x):
return 2*x
g = add_val(10)(f)
[EDIT]
You may also improve the decorator with functools.wraps() (which is a decorator itself). All you need to do is to change your wrapper declaration:
#functools.wraps(f)
def wrapper(cls, _x):
return f(cls, _x) + _value
You can write a simple function outside the class:
def add_val(f):
def wrapper(cls, _x):
return f(cls, _x) + 10
return wrapper
class A:
#add_val
def f(self, x):
return 2*x
print(A().f(20))
Output:
50
Edit: you can utilize functools.wraps with a classmethod in Python3. The wrapped function f will return x*2 and the decorator g will add 10 to the returned result of the function passed to it. However, to be able to save the original functionality of f, you can utilize the __wrapped__ attribute:
import functools
def g(f):
#functools.wraps(f)
def wrapper(cls, _x):
return f(cls, _x)+10
return wrapper
class A:
#classmethod
#g
def f(cls, x):
return x*2
f_1 = functools.partial(A.f.__wrapped__, A)
print(A.f(4))
print(f_1(4))
Output:
18
8

Decorate operators python3.5

I'm trying to decorate all methods in class and i succeded with this code, but i'm also trying to log calls to operators like * + - / , is there any way to decorate them or something like getattr(self,"*") to log the calls ?
class Logger(object):
def __init__(self, bool):
self.bool = bool
def __call__(self, cls):
class DecoratedClass(cls):
def __init__(cls, *args, **kwargs):
super().__init__(*args, **kwargs)
if not(self.bool):
return
methods = [func for func in dir(cls)
if callable(getattr(cls, func))
and not func.startswith("__class")]
for func in methods:
old_func = getattr(cls, func)
def decorated_function(fname, fn):
def loggedFunction(*args, **kwargs):
print("Calling {0} from {3} with params {1} and kwargs {2}".format(fname.upper(), args, kwargs, cls))
return fn(*args, **kwargs)
return loggedFunction
setattr(cls, func, decorated_function(func, old_func))
return DecoratedClass
#Logger(True)
class DummyClass():
def __init__(self,foo):
self.foo = foo
def bar(self):
print(self.foo)
def __mul__(self,other):
print("Hello",other)
if __name__ == '__main__':
a = DummyClass('hola')
a.method()
a.__mul__(a) #this is logged
print(a*a) #this is not logged by decorator
Thanks to Ɓukasz, here is a working script.
A difficulty I encountered is to handle multiple instances and avoid to decorate multiple times the same class methods. To handle this problem, I keep track of the decorated class methods (cls.__logged).
Another difficulty is to deal with the magic methods like __setattr__, __getattribute__, __repr__, ... My solution is to ignore them, except for a list that you must define at start (loggable_magic_methods).
from functools import wraps
loggable_magic_methods = ['__mul__',]
def is_magic_method(method):
return method.startswith('__')
class Logger(object):
def __init__(self, bool):
self.bool = bool
def __call__(self, cls):
class LoggedClass(cls):
cls.__logged = []
def __init__(instance, *args, **kwargs):
super().__init__(*args, **kwargs)
if not(self.bool):
return
methods = [funcname for funcname in dir(instance)
if callable(getattr(instance, funcname))
and (funcname in loggable_magic_methods or not is_magic_method(funcname))]
def logged(method):
#wraps(method)
def wrapper(*args, **kwargs):
print (method.__name__, args, kwargs, cls)
return method(*args, **kwargs)
return wrapper
for funcname in methods:
if funcname in cls.__logged:
continue
if is_magic_method(funcname):
setattr(cls, funcname, logged(getattr(cls, funcname)))
cls.__logged.append(funcname)
else:
setattr(instance, funcname, logged(getattr(instance, funcname)))
return LoggedClass
#Logger(True)
class DummyClass():
def __init__(self, foo, coef):
self.foo = foo
self.coef = coef
def bar(self):
print(self.foo)
def __mul__(self, other):
print(self.foo)
print(other.foo)
return self.coef * other.coef
if __name__ == '__main__':
a = DummyClass('hola', 1)
a.bar()
print()
print(a.__mul__(a))
print()
print(a*a)
print()
b = DummyClass('gracias', 2)
b.bar()
print()
print(b.__mul__(a))
print()
print(b*a)
Currently you are patching values on instance. Your usage of cls in __init__ signature is false friend - actually it's old plain self in this case.
If you want to override magic methods, interpreter looks for them on class objects, not on instances.
Minimal example:
class DummyClass:
def __init__(self, foo):
self.foo = foo
def __mul__(self, other):
return self.foo * other.foo
def logged(method):
def wrapper(*args, **kwargs):
print (method.__name__, args, kwargs)
return method(*args, **kwargs)
return wrapper
DummyClass.__mul__ = logged(DummyClass.__mul__)
a = DummyClass(1)
b = DummyClass(2)
assert a * a == 1
assert a * b == 2
assert b * b == 4
Each call is logged.
>>> a = DummyClass(1)
>>> b = DummyClass(2)
>>> assert a * a == 1
__mul__ (<__main__.DummyClass object at 0x00000000011BFEB8>, <__main__.DummyClass object at 0x00000000011BFEB8>) {}
>>> assert a * b == 2
__mul__ (<__main__.DummyClass object at 0x00000000011BFEB8>, <__main__.DummyClass object at 0x00000000011BF080>) {}
>>> assert b * b == 4
__mul__ (<__main__.DummyClass object at 0x00000000011BF080>, <__main__.DummyClass object at 0x00000000011BF080>) {}
I'll leave a task of rewriting monkey-patching approach to you.

Python __getattribute__ and wrapper of method

I want to call a wrapper method of function with argument just before I call specific method.
So I guess I have to ovewrite __getattribute__ method.
Here is an example of code:
def wrapper(func):
return func * 2
class A(object):
def test(self, arg):
return arg + 1
def __getattribute__(self, name):
if name in ['test']:
return wrapper(super(A, self).__getattribute__(name))
return super(A, self).__getattribute__(name)
The matter is that getattribute is called when test return the value. What I want is to be able to catch test with the arguments and define the wrapper method like this:
def wrapper(func, *args, **kwargs):
print "do some stuff"
return func(*args, **kwargs)
Use a factory function to return your wrapper:
def decorate(f):
def wrapper(*args, **kw):
return f(*args, **kw) * 2
return wrapper
Here f is closed over by wrapper(), so it can access the name when called.
Then return this in the __getattribute__ hook:
def __getattribute__(self, name):
result = super(A, self).__getattribute__(name)
if name in ('test',):
return decorate(result)
return result
Of course, you could also just apply decorate as a decorator on test then:
class A(object):
#decorate
def test(self, arg):
return arg + 1
If I understand you correctly, you can use a decorator.
def wrapper(func):
def _wrapper(*args, **kwargs):
return func(*args, **kwargs) * 2
return _wrapper
class A(object):
#wrapper
def test(self, arg):
return arg + 1

Categories