Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Making decorators with optional arguments [duplicate]

from functools import wraps  def foo_register(method_name=None):     """Does stuff."""     def decorator(method):         if method_name is None:             method.gw_method = method.__name__         else:             method.gw_method = method_name         @wraps(method)         def wrapper(*args, **kwargs):             method(*args, **kwargs)         return wrapper     return decorator 

Example: The following decorates my_function with foo_register instead of ever making it to decorator.

@foo_register def my_function():     print('hi...') 

Example: The following works as expected.

@foo_register('say_hi') def my_function():     print('hi...') 

If I want it to work correctly in both applications (one using method.__name__ and one passing the name in), I have to check inside of foo_register to see if the first argument is a decorator, and if so, I have to: return decorator(method_name) (instead of return decorator). This sort of "check to see if it's a callable" seems very hackish. Is there a nicer way to create a multi-use decorator like this?

P.S. I already know that I can require the decorator to be called, but that's not a "solution". I want the API to feel natural. My wife loves decorating, and I don't want to ruin that.

like image 547
orokusaki Avatar asked Oct 08 '10 06:10

orokusaki


People also ask

Can a function have 2 decorators?

So, here in this post, we are going to learn about Decorator Chaining. Chaining decorators means applying more than one decorator inside a function. Python allows us to implement more than one decorator to a function.

Can decorators take arguments Python?

Implementing Decorator Arguments You may expect that decorator arguments are somehow passed into the function along with this f argument, but sadly Python always passes the decorated function as a single argument to the decorator function.

Can decorators be async?

Not using async also gives the advantage of being able to use the same decorator in normal functions as well. The only advantage of having async with a single return await is to make it clearly documented the function must be awaited, but that doesn't matter in a decorator wrapper.

Are decorators Pythonic?

Decorators are a very powerful and useful tool in Python since it allows programmers to modify the behaviour of a function or class. Decorators allow us to wrap another function in order to extend the behaviour of the wrapped function, without permanently modifying it.


1 Answers

The cleanest way I know of for doing this is the following:

import functools   def decorator(original_function=None, optional_argument1=None, optional_argument2=None, ...):      def _decorate(function):          @functools.wraps(function)         def wrapped_function(*args, **kwargs):             ...          return wrapped_function      if original_function:         return _decorate(original_function)      return _decorate 

Explanation

When the decorator is called with no optional arguments like this:

@decorator def function ... 

The function is passed as the first argument and decorate returns the decorated function, as expected.

If the decorator is called with one or more optional arguments like this:

@decorator(optional_argument1='some value') def function .... 

Then decorator is called with the function argument with value None, so a function that decorates is returned, as expected.

Python 3

Note that the decorator signature above may be improved with Python 3-specific *, syntax to enforce safe use of keyword arguments. Simply replace the signature of the outermost function with:

def decorator(original_function=None, *, optional_argument1=None, optional_argument2=None, ...): 
like image 147
benavente Avatar answered Sep 20 '22 08:09

benavente