Reputation: 175
I'm learning about decorators and came across an example where the decorator took an argument. This was a little confusing for me though, because I learned that (note: the examples from this question are mostly from this article):
def my_decorator(func):
def inner(*args, **kwargs):
print('Before function runs')
func(*args, **kwargs)
print('After function ran')
return inner
@my_decorator
def foo(thing_to_print):
print(thing_to_print)
foo('Hello')
# Returns:
# Before function runs
# Hello
# After function ran
was equivalent to
foo = my_wrapper(foo)
So, it doesn't make sense to me how something could take an argument, to better explain, here is a decorator example that takes an argument:
def repeat(num_times):
def decorator_repeat(func):
@functools.wraps(func)
def wrapper_repeat(*args, **kwargs):
for _ in range(num_times):
value = func(*args, **kwargs)
return value
return wrapper_repeat
return decorator_repeat
@repeat(num_times=4)
def greet(name):
print(f"Hello {name}")
greet('Bob')
# Returns:
# Hello Bob
# Hello Bob
# Hello Bob
# Hello Bob
So when I see this I am thinking:
greet = repeat(greet, num_times=4)
I know that that can't be right because num_times
is the only argument that should be getting passed. So what is the correct equivalent to @repeat(num_times=4)
without the "@
-symbol-syntax"? Thanks!
Upvotes: 8
Views: 1238
Reputation: 536
it will be better if you use
from typing import Callable
def func(*args, **kwargs):
pass
def repeat(times: int, func: Callable, *args, **kwargs):
for __ in range(times):
value = func(*args, **kwargs)
return value
# usage: repear(4, func, ...)
then you can make it be another form to make repeat
return a function:
def repeat(times: int, func: Callable) -> Callable:
# the function that you really have to have:
def inner(*args, **kwargs):
for __ in times:
value = func(*args, **kwargs)
return valur
# return the function that you really want
return inner
# usage: repeat(4, func)(...)
Fine, but now you think decorator will be better:
def repeat(func: Callable) -> Callable:
# the function that you really have to have:
def inner(*args, **kwargs):
for __ in times:
value = func(*args, **kwargs)
return valur
# return the function that you really want
return inner
now you can use it like:
times = 4
@repeat
def func(*args, **kwargs):
pass
but it do ugly, how about making this be a strong function? Good idea.
def outer_repeat(times: int):
return repeat
now you can work like this:
@outer_repeat(times = 4)
def func(*args, **kwargs):
pass
so the finally solution will like this:
def out_repeat(times: int):
def repeat(func: Callable):
# the function that you want to have
@functools.wraps(func)
def inner(*args, **kwargs):
for __ in range(times):
value = func(*args, **kwargs)
return value
# return the function that you wanna
return inner
return repeat
Upvotes: 1
Reputation: 1218
That article is the same one that tought me everthing I know about decorators! It's brilliant. In regards to what the non @
symbol syntax looks like:
You can imagine the actual decorator function is decorator_repeat(func)
, the function within repeat(num_times=4)
.
@repeat(num_times=4)
returns a decorator which is essentially @decorator_repeat
except @decorator_repeat
now has access to a variable num_times
.
Further down the page in the article it shows how to make these arguments optional which may help further clarify it for you.
Upvotes: 3
Reputation: 16623
In this case it would be:
greet = repeat(num_times=4)(greet)
This would explain the two levels of nesting within repeat
(you need to call the function "twice," you could say). repeat(num_times=4)
returns a decorator, then that decorator wraps around greet
.
Upvotes: 5
Reputation: 3031
From your example code, repeat
returns decorator_repeat
definition.
So you can call decorator_repeat
function and pass in greet
function as the following:
def greet(name):
print(f"Hello {name}")
greet = repeat(num_times=4)(greet)
greet('Bob')
# Hello Bob
# Hello Bob
# Hello Bob
# Hello Bob
Upvotes: 3