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
Related
I am developing an API through which I am passing to the user list of functionalities of a module with the documentations of each function. In order to access the documentation I used to do:
def foo(*args, **kwargs):
"""
Foo documentation is here!
"""
return None
print(foo.__doc__)
# Foo documentation is here!
Now that I added a decorator for some of those functions, the __doc__ returns None since the decorator function doesn't have any documentation.
def decor_func(func):
def wrap(*args, **kwargs):
return func(*args, **kwargs)
return wrap
#decor_func
def foo(*args, **kwargs):
"""
Foo documentation is here!
"""
return None
print(foo.__doc__)
# None
Is there any way that I can have access to decorated function's documentation?
You can update the __doc__ attribute of the wrap function:
def decor_func(func):
def wrap(*args, **kwargs):
return func(*args, **kwargs)
# Set the decorated function `__doc__` attribute
wrap.__doc__ = func.__doc__
return wrap
#decor_func
def foo(*args, **kwargs):
"""
Foo documentation is here!
"""
return None
print(foo.__doc__)
# Foo documentation is here!
However, the best approach is to use functools.wraps, as allows you to also copy additional attributes such as the original name, module and annotations:
import functools
def decor_func(func):
#functools.wraps(func)
def wrap(*args, **kwargs):
return func(*args, **kwargs)
return wrap
#decor_func
def foo(*args, **kwargs):
"""
Foo documentation is here!
"""
return None
print(foo.__doc__)
# Foo documentation is here!
Note, as others have pointed out, you should use functools.wraps so that your wrapper "looks" like the function it is wrapping, and adds the wrapped fucntion to a __wrapped__ attribute. However, note, you can always introspect the wrapper's closure to retrieve a reference to the original function, since it is a free variable in the wrapper and thus will be stored in the closure:
>>> def decor_func(func):
... def wrap(*args, **kwargs):
... return func(*args, **kwargs)
... return wrap
...
>>> #decor_func
... def foo(*args, **kwargs):
... """
... Foo documentation is here!
... """
... return None
...
>>> foo.__closure__
(<cell at 0x10e69da90: function object at 0x10e83a700>,)
So,
>>> foo.__closure__[0].cell_contents.__doc__
'\n Foo documentation is here!\n '
But again, you should use functools.wraps to begin with. The above might help if you have no control over the decorator though.
I am writing a bunch of code that has a possibility of mutable outputs, like an arithmetic function where I could have the output be a float or an int. Basically my problem is that if I were to create a decorator for each object type I need (probably seven or eight), I would go insane with the constant repetition of:
def Int(fn):
def wrapper():
return int(fn())
return wrapper
What I want to have is a class like below that would create a decorator based on the name it's instantiated with and it would be a copy of the function above but with the appropriate type modifications.
class Decorator(object):
def __init__(self):
...
...
Int = Decorator()
# Then I can use #Int
Any help would be really appreciated. Thanks.
You cannot have Decorator know what name it will be assigned to. Assignment occurs after instantiation, so the object will have already been created by the time it is assigned a name.
You could however make a decorator that creates decorators dynamically:
from functools import wraps
def set_return_type(typeobj):
def decorator(func):
#wraps(func)
def wrapper(*args, **kwargs):
return typeobj(func(*args, **kwargs))
return wrapper
return decorator
You would then use this decorator by giving a type object argument for the type you want:
#set_return_type(int) # Causes decorated function to return ints
#set_return_type(float) # Causes decorated function to return floats
Below is a demonstration:
>>> from functools import wraps
>>> def set_return_type(typeobj):
... def decorator(func):
... #wraps(func)
... def wrapper(*args, **kwargs):
... return typeobj(func(*args, **kwargs))
... return wrapper
... return decorator
...
>>> #set_return_type(float)
... def test():
... return 1
...
>>> test()
1.0
>>>
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.
def decor(fun):
def living(*args, **kw):
return fun(*args, **kw)
return living
#decor
def test():
'''function doc'''
pass
print test.__doc__
why the result is None? Something happened when I use decorator? Thanks to answer!
Because when you wrap a function in a decorator:
#decor
def test:
you get back the function created by the decorator, (living, in this case) which doesn't have the same docstring, etc. It doesn't "lose" this data, living never had it!
You can get around this with functools.wraps:
from functools import wraps
def decor(fun):
#wraps(fun)
def living(*args, **kw):
...
return func
A quick demo to prove the point:
>>> def wrapper(f):
def func(*args):
"""The wrapper func's docstring."""
return f(*args)
return func
>>> #wrapper
def test(x):
"""The test func's docstring."""
return x ** 2
>>> test.__doc__
"The wrapper func's docstring."
versus
>>> from functools import wraps
>>> def wrapper(f):
#wraps(f)
def func(*args):
"""The wrapper func's docstring."""
return f(*args)
return func
>>> #wrapper
def test(x):
"""The test func's docstring."""
return x ** 2
>>> test.__doc__
"The test func's docstring."
That's because your decorator is basically replacing your function. You need to use functools.wraps() to store the decorated functions internals like __name__ and __doc__.
You can test this easily by adding a docstring to your decorator function living():
>>> def decor(fun):
... def living(*args, **kw):
... """This is the decorator for living()"""
... return fun(*args, **kw)
... return living
...
>>> #decor
... def test():
... """function doc"""
... pass
...
>>> test.__doc__
'This is the decorator for living()'
Example from the docs of functools.wraps() which saves the wrapped functions name and docstring.
>>> from functools import wraps
>>> def my_decorator(f):
... #wraps(f)
... def wrapper(*args, **kwds):
... print 'Calling decorated function'
... return f(*args, **kwds)
... return wrapper
...
>>> #my_decorator
... def example():
... """Docstring"""
... print 'Called example function'
...
>>> example()
Calling decorated function
Called example function
>>> example.__name__
'example'
>>> example.__doc__
'Docstring'
It seems that mymethod is not yet a method when the decorator is called.
import inspect
class decorator(object):
def __call__(self, call):
if inspect.ismethod(call): #Not working yet
obj = "method"
args = inspect.getargspec(call)[0][1:]
elif inspect.isfunction(call):
obj = "function"
args = inspect.getargspec(call)[0]
elif inspect.isclass(call):
obj = "class"
args = inspect.getargspec(call.__init__)[0][1:]
args="(%s)" % repr(args)[1:-1].replace("'","")
print "Decorate %s %s%s" % (obj, call.__name__, args)
return call
#decorator()
def myfunction (a,b): pass
#decorator()
class myclass():
def __init__(self, a, b): pass
#decorator()
def mymethod(self, a, b): pass
if inspect.isfunction(myclass.mymethod):
print "mymethod is a function"
if inspect.ismethod(myclass.mymethod):
print "mymethod is a method"
Output:
Decorate function myfunction(a, b)
Decorate function mymethod(self, a, b)
Decorate class myclass(a, b)
mymethod is a method
I would know if the first argument is 'self', but there will be a less dirty solution?
Edit: Why?
I want to populate a list of callables and their arguments, if it is a function or a class, and I can pass the expected arguments, then I call it, but if it is a method, I have no "self" argument to pass. Something like:
import inspect
class ToDo(object):
calls=[]
def do(self, **kwargs):
for call in self.calls:
if 'self' in call.args:
print "This will fail."
args = {}
for arg in call.args:
args[arg]=kwargs.get(arg, None)
call.call(**args)
TODO = ToDo()
class decorator(object):
def __call__(self, call):
if inspect.isfunction(call):
args = inspect.getargspec(call)[0]
elif inspect.isclass(call):
args = inspect.getargspec(call.__init__)[0][1:]
self.call = call
self.args = args
TODO.calls.append(self)
return call
TODO.do(a=1, b=2)
You can't really make that distinction. Here's an example:
>>> class A(object):
... pass
...
>>> def foo(x): return 3
...
>>> A.foo = foo
>>> type(foo)
<type 'function'>
>>> type(A.foo)
<type 'instancemethod'>
As you can see, your decorator could apply to foo, since it is a function. But you can then simply create a class attribute that references the function to create a decorated method.
(This example is from Python 2.7; I'm not sure if anything has changed in Python 3 to make the above behave differently.)
You cannot tell method from function but you can check if first argument looks like self:
def __call__(self, func):
def new_func(*args, **kw):
if len(args) and hasattr(args[0], '__dict__') \
and '__class__' in dir(args[0]) and func.__name__ in dir(args[0])\
and '__func__' in dir(getattr(args[0], func.__name__))\
and getattr(args[0], func.__name__).__func__ == self.func:
return my_func(*args[1:], **kw)
else:
return my_func(*args, **kw)
self.func = new_func
return new_func
But that won't work for nested decorators - next decorator will change the function and comparison with self.func won't work.
Another approach - to check if first argument's name of decorated function is self - this is
very strong convention in Python so may be good enough:
def __call__(self, func):
def new_func(*args, **kw):
if len(inspect.getfullargspec(func).args)\
and inspect.getfullargspec(func).args[0] == 'self':
return my_func(*args[1:], **kw)
else:
return my_func(*args, **kw)
self.func = new_func
return new_func