0

I have always had trouble really understanding some of Python’s advanced concepts. I just thought I finally understood decorators and ran into another wall.

As I understand, the core truth to decorators is that the following two things are exactly the same:

@function_generator
def original_function:
    ...

and

original_function = function_generator(original_function)

which is to say that what is being done is that function_generator is being called and passed the original_function, then function_generator hands back a different function which then is given the original function’s name, so that when the code calls the original_function, what really gets executed is the code the function generator returns.

But then I tried this:

def decorator(passed_func, times):
    """the decorator"""
    def replacement():
        """the replacement"""
        for i in range(times):
            passed_func()
    return replacement

#@decorator
def print_name():
    """prints my name"""
    print("My name is Benn")

iterate = int(input("How many times?"))
print_name = decorator(print_name, iterate)
print_name()

This code works fine and does exactly what I expect. But when I uncomment the @decorator and comment out print_name = decorator(print_name, iterate), it crashes.

The reason I ran this code test is because I was seeing an advanced example of decorators where they said we have to wrap the decorator in a whole other function to pass in arguments (like iterate). However, it seems to me that if that is true it makes the statement that @func2 is merely syntactic sugar for typing func1=func2(func1) into a lie.

So which of the following is true:

  1. typing func1=func2(func1) is NOT exactly the same as @func2 over the def for func1, because if it was, then if func1=func2(func1,arg) works then @func2 as a decorator would work; or

  2. they are the same even though from the above example it seems exactly the opposite – if so, why does it seem the opposite?

That's what I am hung up on right now. Why do so many people say that @decorator is nothing but another way to write func1=decorator(func1) when it does not work that way when you add an argument in to the mix?

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
Sindyr
  • 1,277
  • 1
  • 11
  • 16
  • I don't know how you've reached the conclusions you have. `@b` on `def a():` being the same as `a = b(a)` does not imply that `a = b(a, c)` will also work with that syntax; where do you suppose `times` is coming from in the `@decorator` form? Perhaps you should look at e.g. https://stackoverflow.com/q/5929107/3001761 to see how to create decorators that take parameters. – jonrsharpe Oct 07 '17 at 13:19
  • What do you call "crash"? – keepAlive Oct 07 '17 at 13:20
  • Hint: A decorator takes a _single_ arg: the function to decorate. But your decorator asks for _two_ args. So where''s it going to get that `times` arg from? – PM 2Ring Oct 07 '17 at 13:21
  • 1
    @Kanak when they use the decorator form, it will complain that the required second positional argument is not being supplied. – jonrsharpe Oct 07 '17 at 13:23

2 Answers2

0

That's because actually you still didn't understand decorator well. If you want to pass parameters to a decorator, the decorator should nested like this, with the parameter at the entry level, and the func is passed in the inner level.

def decorator(times):
    def wrapper(passed_func):
        """the wrapper"""

        def replacement():
            """the replacement"""
            for i in range(times):
                passed_func()

        return replacement

    return wrapper


# @decorator(3)
def print_name():
    """prints my name"""
    print("My name is Benn")


iterate = int(input("How many times?"))
print_name = decorator(iterate)(print_name)
print_name()

You can uncomment the decorator and try as you wish.

Menglong Li
  • 2,177
  • 14
  • 19
0

One way you can do this is to create a decorator factory: a function that returns a decorator. The syntax for calling it looks like a decorator with an argument list. Here's a modified version of your code:

def decorator_factory(times):
    def decorator(passed_func):
        """the decorator"""
        def replacement():
            """the replacement"""
            for i in range(times):
                passed_func()
        return replacement
    return decorator

iterates = int(input("How many times? "))

@decorator_factory(iterates)
def print_name():
    """prints my name"""
    print("My name is Benn")

print_name()

demo

How many times? 4
My name is Benn
My name is Benn
My name is Benn
My name is Benn

It may help to understand what's happening if we re-write the last part of that code like this:

dec = decorator_factory(iterates)
@dec
def print_name():
    """prints my name"""
    print("My name is Benn")

So decorator_factory(iterates) returns dec, and then dec is used to decorate print_name.

PM 2Ring
  • 54,345
  • 6
  • 82
  • 182