Wrapper for Python Decorators - python

Lately, I have started using decorators more extensively. For a project, I require different decorators which I built like in the schema below:
def param_check(fn=None, arg1=None, arg2=None):
def deco(fn):
#wraps(fn)
def wrapper(*args, **kwargs):
#this is the only part that really changes
result = func(arg1, arg2)
return fn(result, *args, **kwargs)
return wrapper
if callable(fn): return deco(fn)
return deco
def func(arg1, arg2):
...do something...
.
.
...return something
However, since this is a lot of repeating code I was wondering what would be the most pythonic way to build a wrapper function that only takes in a function, and then returns a new decorator? I tried several ways but had no success. I was wondering if someone knows how to do that.

I think you just want to add another layer: the outermost layer will take an argument, and that's what gets called inside wrapper, not a hard-coded reference to func.
def decorator_maker(decfun):
def parameterized_decorator(fn=None, arg1=None, arg2=None):
def deco(fn):
#wraps(fn)
def wrapper(*args, **kwargs):
#this is the only part that really changes
result = decfun(arg1, arg2)
return fn(result, *args, **kwargs)
return wrapper
if callable(fn): return deco(fn)
return deco
return parameterized_decorator
def func(arg1, arg2):
pass
param_check = decorator_maker(func)
# Or...
# #decorator_maker
# def func(arg1, arg2):
# ...
#param_check(3, 4)
def f(result):
...

Related

Python decorator in a class: missing required positional arguments:

class Something:
def __init__(self, ...):
...
def update(self):
...
def add_update(self, func):
def fct(*args, **kwargs):
self.update()
func(*args, **kwargs)
return fct
#add_update
def method(self, some_parameter):
...
So basically I have this class, and I want to call the function "update()" automatically before I call a method from the class. But I get this error:
TypeError: add_update() missing 1 required positional argument: 'func'
I don't really understand what's wrong here, also I saw some tutorials on the internet where they did something similar and it was working. Can someone explain me what's wrong here and how do I fix it?
What is a decorator? It's syntactic sugar for more verbose syntax.
#decorator
def my_fun():
...
is the same as
my_fun = decorator(my_fun)
So in your specific case
method = add_update(method)
Do you see a problem? Add update expects two parameters (self, and func), but here it gets only one (and actually you pass method as self).
To solve this you need to create decorator outside the class that gets only function, and that functions first parameter will be self:
def add_update(func):
def wrapper(*args):
self = args[0]
...
when you use #add_update, It is equivalent to add_update(method), At this time add_update is still a function.
You can make the following modifications to make it work
class Something:
def __init__(self):
...
def update(self):
...
def add_update(f):
def fct(self, *args, **kwargs):
self.update()
return f(self, *args, **kwargs)
return fct
#add_update
def method(self, some_parameter):
...
You have to pass the argument func and define the class inside the decorator function.
class Something:
def __init__(self,..):
...
def update(self):
...
def add_update( func):
def fct(*args, **kwargs):
args[0].update() #or Something.update()
func(*args, **kwargs)
return fct
#add_update
def method(self, some_parameter):
...

Class decorators for methods in classes

How do class decorators for methods in classes work? Here is a sample of what I've done through some experimenting:
from functools import wraps
class PrintLog(object):
def __call__(self, func):
#wraps(func)
def wrapped(*args):
print('I am a log')
return func(*args)
return wrapped
class foo(object):
def __init__(self, rs: str) -> None:
self.ter = rs
#PrintLog()
def baz(self) -> None:
print('inside baz')
bar = foo('2')
print('running bar.baz()')
bar.baz()
And this works perfectly fine. However, I was under the impression that decorators do not need to be called with (), but when I remove the brackets from #PrintLog(), I get this error:
def baz(self) -> None:
TypeError: PrintLog() takes no arguments
Is there something I am missing/do not understand? I've also tried passing in a throwaway arg with __init__(), and it works.
class PrintLog(object):
def __init__(self, useless):
print(useless)
def __call__(self, func):
#wraps(func)
def wrapped(*args):
print('I am a log')
return func(*args)
return wrapped
class foo(object):
def __init__(self, rs: str) -> None:
self.ter = rs
#PrintLog("useless arg that I'm passing to __init__")
def baz(self) -> None:
print('inside baz')
Again, this works, but I don't want to pass any argument to the decorator.
tl;dr: This question in python 3.x.
Help appreciated!
Class decorators accept the function as a subject within the __init__ method (hence the log message), so your decorator code should look like:
class PrintLog(object):
def __init__(self, function):
self.function = function
def __call__(self):
#wraps(self.function)
def wrapped(*args):
print('I am a log')
return self.function(*args)
return wrapped
Sorry if this doesn’t work, I’m answering on my mobile device.
EDIT:
Okay so this is probably not what you want, but this is the way to do it:
from functools import update_wrapper, partial, wraps
class PrintLog(object):
def __init__(self, func):
update_wrapper(self, func)
self.func = func
def __get__(self, obj, objtype):
"""Support instance methods."""
return partial(self.__call__, obj)
def __call__(self, obj, *args, **kwargs):
#wraps(self.func)
def wrapped(*args):
print('I am a log')
return self.func(*args)
return wrapped(obj, *args)
class foo(object):
def __init__(self, rs: str) -> None:
self.ter = rs
#PrintLog
def baz(self) -> None:
print('inside baz')
bar = foo('2')
print('running bar.baz()')
bar.baz()
The decorator has to have the __get__ method defined because you're applying the decorator to an instance method. How would a descriptor have the context of the foo instance?
Ref: Decorating Python class methods - how do I pass the instance to the decorator?
There is a big picture you're missing.
#decorator
def foo(...):
function_definition
is almost identical (except for some internal mangling) to
temp = foo
foo = decorator(temp)
It doesn't matter what the decorator is, as long as it can act like a function.
Your example is equivalent to:
baz = PrintLog("useless thing")(<saved defn of baz>)
Since PrintLog is a class, PrintLog(...) creates an instance of PrintLog. That instance has a __call__ method, so it can act like a function.
Some decorators are designed to take arguments. Some decorators are designed not to take arguments. Some, like #lru_cache, are pieces of Python magic which look to see if the "argument" is a function (so the decorator is being used directly) or a number/None, so that it returns a function that then becomes the decorator.

Log decorator for methods

I'm trying to create a decorator which logs time when method was started, name of the method and arguments of this method.
This decorator works in some situations but I want to make it work in as much situations as possible. So I want to make it work both as a normal method and as a class method.
def log(func):
#wraps(func)
def wrapper(*args, **kwargs):
log(func.__name__ + '; ARGS: {}'.format(','.join(str(args))))
return func(*args, **kwargs)
return wrapper
There are some problems:
It does not work for class methods. To make it work in a class I would have to put self as a first argument here def wrapper(*args, **kwargs): and here return func(*args, **kwargs).
I want to make it work for all types of arguments (lists,strings,ints,dicts etc.). This works only for strings and ints (respectively for floats etc.)
The weird thing is that if I use it for example on a method which takes two integers as an arguments, it prints this to the log: name_of_function; ARGS: (,5,,, ,6,)
I would appreciate if you help me to solve any of these problems.
Does this help?
def func_detail(func):
def func_wrapper(*args,**kwargs):
print(func.__name__)
print(*args)
print(kwargs)
return func(*args,**kwargs)
return func_wrapper
#func_detail
def foo(a,b,c,**kwargs):
return 1
class Person(object):
#func_detail
def __init__(self,name,age,**kwargs):
self.name = name
self.age = age
self.email = kwargs.get('email',None)
me = Person('taesu',24,email='me#me.com')
print(foo(1,1.1,'4',test='me'))

Obtaining *args and **kwargs from passed func

In the below code, how would I obtain *args and **kwargs in function f without the need for the wrapper function?
def f(func):
def wrapper(*args, **kwargs):
print(args)
print(kwargs)
return func(*args,**kwargs)
return wrapper
#f
def write(text):
print(text)
# write = a(write)
write('dog')
Failed attempt 1:
def f(func):
a=func(*args)
k=func(**kwargs)
which causes error:
NameError: global name 'args' is not defined
Failed attempt 2:
def f(func(*args,**kwargs)):
a=func(*args)
k=func(**kwargs)
The wrapper function is necessary, and a standard part of how decorator definitions in Python work.
You can, however, help mask the existence of the wrapper function in tracebacks by using functools.wraps():
import functools
def f(func):
#functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
This will update the wrapper function to have the name and docstring of the wrapped function.
--
Decorators are nothing more than functions which are passed a function. This code...
def dec(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
#dec
def myfunc(foo, bar):
return foo+bar
is equivalent to this code:
def dec(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
def myfunc(foo, bar):
return foo+bar
myfunc = dec(myfunc)
Notice how the thing being passed to dec is a function which hasn't even been called yet - so there aren't any arguments passed at the time when dec is invoked. This is why the wrapper function is involved: it adds a layer which will be called when the original function is invoked which can capture arguments.

How do I make pytest fixtures work with decorated functions?

py.test seems to fail when I decorate test functions which has a fixture as an argument.
def deco(func):
#functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
#pytest.fixture
def x():
return 0
#deco
def test_something(x):
assert x == 0
In this simple example, I get the following error:
TypeError: test_something() takes exactly 1 argument (0 given).
Is there a way to fix this, preferably without modifying the decorator too much? (Since the decorator is used outside testing code too.)
It looks like functools.wraps does not do the job well enough, so it breaks py.test's introspection.
Creating the decorator using the decorator package seems to do the trick.
import decorator
def deco(func):
def wrapper(func, *args, **kwargs):
return func(*args, **kwargs)
return decorator.decorator(wrapper, func)
Fixture feature depends on test function signature.
If you can change wrapper signature as follow, it will works.
def deco(func):
#functools.wraps(func)
def wrapper(x):
return func(x)
return wrapper
If you can't change it, make another decorator:
def deco(func):
#functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
def deco_x(func):
#functools.wraps(func)
def wrapper(x):
return func(x)
return wrapper
And decorate test_somthing with deco_x:
#deco_x
#deco
def test_something(x):
assert x == 0

Categories