How to detect missing arguments in decorator? - python

I would like to define a decorator with a parameter that raises an error if the parameter is missing.
Here's a naive attempt on a simplified example:
def decorator_with_arg(a=None):
if a is None :
raise ValueError("Missing argument in decorator")
def decorator(func):
def wrapped_func(x):
return func(x+ a)
return wrapped_func
return decorator
But when I use this decorator without a parameter it's not raising any errors:
#decorator_with_arg
def simple_func(x):
return 2*x
simple_func(1)
How can I raise an exception?

You are not using your decorator correctly, in your code simple_func(1) will just return wrapped_func, because #decorator_with_arg will simply do:
simple_func = decorator_with_arg(simple_func)
# ^ this is passing a=simple_func
# now simple_func is the decorator function defined inside decorator_with_arg
You need to call your decorator_with_arg in order for it to return decorator which will then be used to decorate the function:
#decorator_with_arg(100)
def simple_func(x):
return 2*x
print(simple_func(1)) # 202
In any case, if you want to make an argument mandatory, simply declare it without a default value:
def decorator_with_arg(a):
# ...
And remove the if a is None check.
If you want to avoid mistakes in using #decorator_with_arg instead of #decorator_with_arg(), you can add a check to ensure a is not a function:
def decorator_with_arg(a):
if callable(a):
raise TypeError("Incorrect use of decorator")
def decorator(func):
def wrapped_func(x):
return func(x + a)
return wrapped_func
return decorator
#decorator_with_arg
def func():
return 1
# TypeError: Incorrect use of decorator
#decorator_with_arg(123)
def func():
return 1
# All fine

Related

How to pass decorator default value to inner function in Python

I'm learning about function decorators and am trying to apply some of the concepts I've learned so far. For the below function i've created a decorator function but it doesnt behave as I would expect.
Can someone help me understand why I can't access the default value (or any provided arg) of the outer function so that I can use it in the inner function? I keep getting a TypeError: type function doesn't define __round__ method.
def mathTool(defaultVal=5):
def innerFunc(*args):
return round(*args, defaultVal)
return innerFunc
print(funcB()) //triggers TypeError
print(funcB(2)) //triggers TypeError
def funcA(A):
return A/2
#mathTool()
def funcB(B):
return B/2
def funcAB(A, B):
return A*B/2
func_dec = mathTool(defaultVal=7)(funcAB)
print(func_dec(5, 9)) //triggers TypeError
Decorators take exactly one argument: the function. And their syntax is this:
#deco
def func():
...
When you see a decorator which takes arguments, as in #deco(arg), that is actually a function which returns a decorator.
Therefore:
def mathTool(defaultVal=5):
def decorator(func):
def innerFunc(*args):
args = args + (defaultVal,)
return func(*args)
return innerFunc
return decorator
#mathTool(7)
def whatever(a, b):
...
Now, calling whatever(1) will pass 1 and 7 to the function.

How to use __func = None in Python decorators

I have the following decorator(as per https://realpython.com/primer-on-python-decorators/#decorators-with-arguments):
def slow_down(_func=None, rate = 1):
def decorator(func):
print(_func) # Added for understanding?
#functools.wraps(func)
def wrapper(*args, **kwargs):
time.sleep(rate)
return func(*args, **kwargs)
return wrapper
if _func==None:
return decorator
else: return decorator(_func)
My question is, shouldn't a this slow_down function initialize with _func = None ? However, when I create a function with the decorator, I get an initialized _func value:
#slow_down
def countdown(time = 5):
print(time)
if time == 0: pass
else: countdown(time-1)
<function countdown at 0x7f1aa0a8da60>
But when I initialize the value of the function, I get None:
#slow_down(rate=2)
def countdown(time = 5):
print(time)
if time == 0: pass
else: countdown(time-1)
None
Shouldn't it be the other way around?
When you use a bare name for a decorator (as in #slow_down), it is called with the function being decorated as a parameter. When parentheses are involved (as in the second case), the decorator is called as written, and is expected to return another function which gets called with the function being decorated as a parameter. The if in your particular decorator allows it to work either way. – jasonharper

Passing arguments to a decorator

I want to pass a list of values to a decorator. Every function which is decorated by the decorator passes different list of values. I am using the decorator python library
Here is what I was trying -
from decorator import decorator
def dec(func, *args):
// Do something with the *args - I guess *args contains the arguments
return func()
dec = decorator(dec)
#dec(['first_name', 'last_name'])
def my_function_1():
// Do whatever needs to be done
#dec(['email', 'zip'])
def my_function_2():
// Do whatever needs to be done
However, this does not work. It gives an error - AttributeError: 'list' object has no attribute 'func_globals'
How can I do this?
You can implement it without decorator library
def custom_decorator(*args, **kwargs):
# process decorator params
def wrapper(func):
def dec(*args, **kwargs):
return func(*args, **kwargs)
return dec
return wrapper
#custom_decorator(['first_name', 'last_name'])
def my_function_1():
pass
#custom_decorator(['email', 'zip'])
def my_function_2():
pass

passing variables between two python decorators

Is there are a way to pass a variable between two python decorators applied to the same function? The goal is for one of the decorators to know that the other was also applied. I need something like decobar_present() from the example below:
def decobar(f):
def wrap():
return f() + "bar"
return wrap
def decofu(f):
def wrap():
print decobar_present() # Tells me whether decobar was also applied
return f() + "fu"
return wrap
#decofu
#decobar
def important_task():
return "abc"
More generally I would like to be able to modify the behavior of decofu depending on whether decobar was also applied.
You can add the function to a "registry" when decobar is applied to it, then later check the registry to determine whether decobar was applied to the function or not. This approach requires preserving original function's __module__ and __name__ properties intact (use functools.wraps over the wrapper function for that).
import functools
class decobar(object):
registry = set()
#classmethod
def _func_key(cls, f):
return '.'.join((f.__module__, f.func_name))
#classmethod
def present(cls, f):
return cls._func_key(f) in cls.registry
def __call__(self, f):
self.registry.add(self._func_key(f))
#functools.wraps(f)
def wrap():
return f() + "bar"
return wrap
# Make the decorator singleton
decobar = decobar()
def decofu(f):
#functools.wraps(f)
def wrap():
print decobar.present(f) # Tells me whether decobar was also applied
return f() + "fu"
return wrap
#decofu
#decobar
def important_task():
return "abc"
Used a class to implement decobar, as it keeps registry and present() in a single namespace (which feels slighly cleaner, IMO)
To pass a variable between two python decorators you can use the decorated function's keyword arguments dictionary. Only don't forget to pop the added argument from there before calling the function from within the second decorator.
def decorator1(func):
def wrap(*args, **kwargs):
kwargs['cat_says'] = 'meow'
return func(*args, **kwargs)
return wrap
def decorator2(func):
def wrap(*args, **kwargs):
print(kwargs.pop('cat_says'))
return func(*args, **kwargs)
return wrap
class C:
#decorator1
#decorator2
def spam(self, a, b, c, d=0):
print("Hello, cat! What's your favourite number?")
return a + b + c + d
x=C()
print(x.spam(1, 2, 3, d=7))
While it is possible to do things like manipulate the stack trace, you're better off, I think, simply creating a function decofubar and incorporate as much of both "fu" and "bar" as possible. At a minimum, it will make your code cleaner and more obvious.
Each decorator gets to wrap another function. The function passed to decofu() is the result of the decobar() decorator.
Just test for specific traits of the decobar wrapper, provided you make the wrapper recognisable:
def decobar(f):
def wrap():
return f() + "bar"
wrap.decobar = True
return wrap
def decofu(f):
def wrap():
print 'decobar!' if getattr(f, 'decobar') else 'not decobar'
return f() + "fu"
return wrap
I used an arbitrary attribute on the wrapper function, but you could try to test for a name (not so unambiguous), for the signature (using inspect.getargspec() perhaps), etc.
This is limited to direct wrapping only.
Generally speaking, you don't want to couple decorators as tightly as all this. Work out a different solution and only depend on function signature or return values.
You can assign flag to f (or rather wrap) in decobar just like this
def decobar(f):
def wrap():
return f() + "bar"
wrap.decobar_applied = True
return wrap
def decofu(f):
def wrap():
if hasattr(f, 'decobar_applied') and f.decobar_applied:
print decobar_present() # Tells me whether decobar was also applied
return f() + "fu"
return wrap
#decofu
#decobar
def important_task():
return "abc"

How to build a decorator with optional parameters? [duplicate]

This question already has answers here:
How to create a decorator that can be used either with or without parameters?
(16 answers)
Making decorators with optional arguments [duplicate]
(14 answers)
Closed 9 years ago.
I would like to make a decorator which could be used with or without a parameter :
Something like this :
class d(object):
def __init__(self,msg='my default message'):
self.msg = msg
def __call__(self,fn):
def newfn():
print self.msg
return fn()
return newfn
#d('This is working')
def hello():
print 'hello world !'
#d
def too_bad():
print 'does not work'
In my code, only the use of decorator with parameter is working: How to proceed to have both working (with and without parameter)?
I found an example, you can use #trace or #trace('msg1','msg2'): nice!
def trace(*args):
def _trace(func):
def wrapper(*args, **kwargs):
print enter_string
func(*args, **kwargs)
print exit_string
return wrapper
if len(args) == 1 and callable(args[0]):
# No arguments, this is the decorator
# Set default values for the arguments
enter_string = 'entering'
exit_string = 'exiting'
return _trace(args[0])
else:
# This is just returning the decorator
enter_string, exit_string = args
return _trace
If you want to take parameters to your decorator, you need to always call it as a function:
#d()
def func():
pass
Otherwise, you need to try to detect the difference in parameters--in other words, you need to magically guess what the caller means. Don't create an API that needs to guess; consistently say what you mean to begin with.
In other words, a function should either be a decorator, or a decorator factory; it shouldn't be both.
Note that if all you want to do is store a value, you don't need to write a class.
def d(msg='my default message'):
def decorator(func):
def newfn():
print msg
return func()
return newfn
return decorator
#d('This is working')
def hello():
print 'hello world !'
#d()
def hello2():
print 'also hello world'
If you don't mind relying on using named arguments, I made something similar to what you need:
def cached_property(method=None, get_attribute=lambda a: '_%s_cached' % (a,)):
"""
Caches an object's attribute.
Can be used in the following forms:
#cached_property
#cached_property()
#cached_property(get_attribute=lambda x: 'bla')
#param method: the method to memoizes
#param get_attribute: a callable that should return the cached attribute
#return a cached method
"""
def decorator(method):
def wrap(self):
private_attribute = get_attribute(method.__name__)
try:
return getattr(self, private_attribute)
except AttributeError:
setattr(self, private_attribute, method(self))
return getattr(self, private_attribute)
return property(wrap)
if method:
# This was an actual decorator call, ex: #cached_property
return decorator(method)
else:
# This is a factory call, ex: #cached_property()
return decorator
This works because only one non keyword argument, the function decorated is passed to the decorator.
Notice that I also used the arguments passed to the decorated function, in this case 'self'.
This would work.
def d(arg):
if callable(arg): # Assumes optional argument isn't.
def newfn():
print('my default message')
return arg()
return newfn
else:
def d2(fn):
def newfn():
print(arg)
return fn()
return newfn
return d2
#d('This is working')
def hello():
print('hello world !')
#d # No explicit arguments will result in default message.
def hello2():
print('hello2 world !')
#d('Applying it twice')
#d('Would also work')
def hello3():
print('hello3 world !')
hello()
hello2()
hello3()
Output:
This is working
hello world !
my default message
hello2 world !
Applying it twice
Would also work
hello3 world !
If a decorator function #invocation isn't passed any explicit arguments, it is called with the function defined in the following def. If it is passed arguments, then it is first called with them and then the result of that preliminary call (which must itself also be a callable) is called with the function being defined. Either way, the return value of the last or only call is bound to the defined function name.
You have to detect if the argument to the decorator is a function, and use a simple decorator in that case. And then you need to hope that you never need to pass only a function to the parametrized decorator.

Categories