ThatComputerGuy
ThatComputerGuy

Reputation: 175

What is the equivalent of decorators with arguments without the syntactical-sugar?

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

Answers (4)

Peterlits Zo
Peterlits Zo

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

GTBebbo
GTBebbo

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

iz_
iz_

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

ywbaek
ywbaek

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

Related Questions