python decorator with arguments of decorated function - python

When I wrap a function with #, how do I make the wrapper function look & feel exactly like the wrapped function? help(function) in particular.
Some code:
>>> def wraps(f):
def call(*args, **kw):
print('in', f, args, kw) # example code. I need to transfer the arguments to another process and pickle them.
return f(*args, **kw)
return call
>>> def g():pass
>>> #wraps
def f(a, b = 1, g = g, *args, **kw):
pass
>>> help(f)
Help on function call in module __main__:
call(*args, **kw) # this line bothers me. It should look different, look below
>>> def f(a, b = 1, g = g, *args, **kw):
pass
>>> help(f)
Help on function f in module __main__:
f(a, b=1, g=<function g at 0x02EE14B0>, *args, **kw) # help(f) should look like this.
Motivation: It would also be nice to see the arguments when the help window pops up, when I type f( * plopp * I see (a, b = 1, g = g, *args, **kw). (in this case in the IDLE Python Shell)
I had a look at the inspect module which helps me with nice formatting. The problem is still there: how do I do this with arguments..
Default mutable argument passing like def f(d = {}): does not need to work since I transfer the arguments to another process and the identity would be lost anyway.

functools.wraps can be used to copy the name and docstring of the function. Copying the original function signature is considerably harder to do from scratch.
If you use the third-party decorator module, however, then
import decorator
#decorator.decorator
def wraps(f):
def call(*args, **kw):
print('in', f, args, kw)
return f(*args, **kw)
return call
def g():pass
#wraps
def f(a, b = 1, g = g, *args, **kw):
pass
help(f)
yields
Help on function f in module __main__:
f(a, b=1, g=<function g>, *args, **kw)

Use functools.wraps:
from functools import wraps
def wrapper(f):
#wraps(f)
def call(*args, **kw):
print('in', f, args, kw)
return f(*args, **kw)
return call
#wrapper
def f(a, b = 1, g = g, *args, **kw):
pass
help(f)
Help on function f in module __main__:
f(a, b=1, g=<function g at 0x7f5ad14a6048>, *args, **kw)
This preserves the __name__ and __doc__ attributes of your wrapped function.

I think the other answers are preferable, but if for some reason you don't want to use an external module, you could always alter your decorator like so:
def wraps(f):
def call(*args, **kw):
print('in', f, args, kw)
return f(*args, **kw)
call.__name__ = f.__name__
call.__doc__ = f.__doc__
return call

Related

Conditional Decorator Implementation

I want to have conditional decorator. Following is my code:-
def int_decorator(func): # checks whether the args passed is of type int.
def wrapper(*args, **kwargs):
a, b = args
if not (isinstance(a, int) and isinstance(b, int)):
return
return func(*args, **kwargs)
return wrapper
decorator_mapping = {
'int': int_decorator
}
class conditional_decorator(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
decorator = decorator_mapping.get(kwargs.get('decorator'))
if not decorator:
# Return the function unchanged, not decorated.
return self.func(*args, **kwargs)
return decorator(self.func(*args, **kwargs))
#conditional_decorator
def func(a, b, *args, **kwargs):
return(1)
print(func(1, 2, decorator='int'))
What I want is if there exist any decorator in decorator_mapping that matches the value passed in the func, then apply the same decorator on the function, else don't apply any decorator.
The above code works well, when there isn;t any decorator, but fails when a decorator is found. It print's the reference of the function.
Where am I wrong?
Decorator accepts a function, returns a function. This line:
return decorator(self.func(*args, **kwargs))
should be:
return decorator(self.func)(*args, **kwargs)
def __call__(self, *args, **kwargs):
decorator = decorator_mapping.get(kwargs.get('decorator'))
if not decorator:
# Return the function unchanged, not decorated.
return self.func(*args, **kwargs)
return decorator(self.func)(*args, **kwargs)
You want to decorate the function, then call the decorated version of it.

how to pass decorated function arguments in decorator

I have code below
#newrelic.agent.data_store_trace('Mysql', '<name>',None)
def get_user(request=None, name=settings.DEFAULT_NAME):
# Some implementation
In the decorator, in place of <name> I want to pass name which is in the decorated function.
Note that I don't want to modify/override the decorator as newrelic updates the packages time-to-time, It would be a problem for us.
Any solution???
Decorators are simply wrapper functions.
Write another wrapper function that wraps newrelic.agent.data_store_trace and allows to pass a name.
Say New Relic defines data_store_trace as:
import functools
def data_store_trace(product, target, operation):
def wraps(fn):
#functools.wraps(fn)
def wrapped(*args, **kwargs):
print('Tracing: ', fn.__name__, args, kwargs)
return fn(*args, **kwargs)
return wrapped
return wraps
Adding another level of indirection will give:
def data_store_trace_with_name(product, operation, target=''):
def wraps(fn):
#functools.wraps(fn)
def wrapped(*args, **kwargs):
return data_store_trace(product, target, operation)(fn)(*args, **kwargs)
return wrapped
return wraps
#data_store_trace_with_name('Mysql', None, '<name>')
def add(x, y):
return x + y
add(5, 6)

Decorator for function with optional arguments

I have a function my_f with many arguments (some of them can be optional), for example,
def my_f(a, b, opt_arg = 3):
#do some stuff
Now I want to write some decorator for this function that will do some simple stuff depending on the optional argument opt_arg. I do not know the number of arguments of my_f but I know the name of the optional argument. How I can do this? Is is it possible to do in such way that both variants my_f(1,2, opt_arg=3) and my_f(opt_arg =3, 1,2,3,4) would work correct?
Use a **kw variable keywords argument in your decorator wrapper and see if opt_arg is present in it:
from functools import wraps
def yourdecorator(f):
#wraps(f)
def wrapper(*args, **kw):
opt_arg = kw.get('opt_arg')
if opt_arg is not None:
print "opt_arg was given and set to {!r}".format(opt_arg)
return f(*args, **kw)
return wrapper
Demo:
>>> from functools import wraps
>>> def yourdecorator(f):
... #wraps(f)
... def wrapper(*args, **kw):
... opt_arg = kw.get('opt_arg')
... if opt_arg is not None:
... print "opt_arg was given and set to {!r}".format(opt_arg)
... return f(*args, **kw)
... return wrapper
...
>>> #yourdecorator
... def my_f(a, b, opt_arg=3):
... pass
...
>>> my_f(42, 81)
>>> my_f(42, 81, opt_arg=3.14)
opt_arg was given and set to 3.14

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.

Get decorated function object by string name

def log(func):
def wraper(*a, **kw):
return func(*a, **kw)
return wraper
#log
def f():
print 'f'
print locals()['f'] # - prints <function wraper at 0x00CBF3F0>.
How do you get the real f object (not decorator wrap)?
The functools module also provides a wraps decorator which makes sure that the wrapped function looks more like the real function: correct name, module, and docstring, for example.
You don't.1 Store it if you need to access it later.
def log(func):
def wrapper(*a, **kw):
return func(*a, **kw)
wrapper.func = func
return wrapper
#log
def f():
print 'f'
print f.func
1 You could mess with the closure, but I can't recommend it.
If you're running python 3.2 or above, and you use functools.wraps then you will find the wrapped function on the __wrapped__ attribute:
from functools import wraps
def log(func):
#wraps(func)
def wrapper(*a, **kw):
return func(*a, **kw)
return wrapper
#log
def f():
print 'f'
print f.__wrapped__
functools.wrapsis a convenience function for decorating a decorated function with the function that does all the work, including adding this attribute functools.update_wrapper.

Categories