0

I am learning Python on Sololearn and I came across this bit of code which intends to decorate a function:

def decor(func):
  def wrap():
    print("============")
    func()
    print("============")
  return wrap

def print_text():
  print("Hello world!")

When I call it like this decor(print_text) I just get this output <function __main__.decor.<locals>.wrap()>

To get the expected, decorated output, I have to call the function with an extra set of empty parentheses, like this decor(print_text)()

Which outputs the correct result:

============
Hello world!
============

I understand that there is an easier way to decorate without this mess, however, I am in it for the long run and I would like to get familiar with how python "thinks". So my question is, why are the extra set of parentheses () required to get the correct output? Why wont just using decor(print_text) give the correct result?

Thanks everyone! I apologize for the long questions but I am trying to explain my thought the best I can :)

Okay y'all I think all my questions regarding this code were answered by the awesome people below. Thanks everyone for your help!

HKn
  • 25
  • 5
  • syntax for decorators is (function goes from new line) @decor def print_text(): ... – Anton Pomieshchenko Apr 02 '20 at 16:59
  • 1
    When you call `decor` you get back the return value from `decor` and that is the function `wrap` (printed as `.wrap()>`). It is the function itself you're getting, there's no execution of this function included. You need to call this function, and to call a function you need the parentheses. – Matthias Apr 02 '20 at 16:59
  • Imagine two steps. 1: `decorated_function = decor(print_text)`. 2: `decorated_function()`. – Matthias Apr 02 '20 at 17:03
  • @Matthias so the entire expression decor(printed_text) sort of transforms to a new function completely, which requires a set of () to execute? – HKn Apr 02 '20 at 17:18

2 Answers2

1

decor(print_text) gives you the correct result… which is a function. To call this function, you need parentheses. Imagine your function took a parameter:

def print_text(x):
  print("Hello " + x + "!")

– how do you suppose the decorated function would be called? Yes, with decor(print_text)("world"). Does it make things clearer?

Błotosmętek
  • 12,717
  • 19
  • 29
  • If you use a parameter you have to change the function `wrap` too. – Matthias Apr 02 '20 at 17:06
  • Okay I think I understand it now. One more thing, when I use decor(print_text()) to call it, it prints out "Hello world!", the expected output of print_text(), and then it outputs .wrap()> Why is the second line printed? How do you "rationalize" what python is doing i mean? – HKn Apr 02 '20 at 17:15
  • 1
    It's `decor(print_text)()`, not `decor(print_text())` - note the parentheses. You must decorate the function and call the decorated function, not decorate the output of the function. – Thierry Lathuille Apr 02 '20 at 17:24
  • @ThierryLathuille I get that. I am just saying, why is the result of print_text() still outputted though before it returns the function indicator .wrap()>? – HKn Apr 02 '20 at 17:28
  • Well, sorry, but you don't - You executed `decor(print_text())` instead of `decor(print_text)()`. – Thierry Lathuille Apr 02 '20 at 17:30
  • @ThierryLathuille I use decor(print_text)() to get expected decorated result. decor(print_text)() is the correct way to call the function, as we are basically executing wrap() with print_text() nested inside it. I understand that. What I am asking is, when using the decoration function incorrectly by typing this decor(print_text()) instead of the correct way of typing this decor(print_text)(), why is the function print_text() is executed and "Hello world!" is outputted before the function indicator (.wrap()>) is displayed? – HKn Apr 02 '20 at 17:37
  • 2
    Because you execute it with `print_text()`, so it prints what it has to print and returns `None`, as no return value was specified. **Then** you call `decor` with this `None` as parameter, and `decor` returns the `wrap` function. Note that you will get an error if you try to run it, as the call to `func()` in the middle of `wrap` will be `None()`, which will fail as `None` isn't callable. – Thierry Lathuille Apr 02 '20 at 17:43
  • @ThierryLathuille Aha! Okay that makes a lot of sense. I didn't account for the print_text() to return None and it being passed as a parameter. Thanks a lot friend! I have a better idea of how Python works now. – HKn Apr 02 '20 at 17:49
1

Your decorator is returning a function. In order to call that function you need parentheses.

def decor(func):
  ...
  return wrap
  ...

If it were:

def decor(func):
  ...
  return wrap()
  ...

Then decor would be returning the result of calling wrap, the function that you defined.

FoxYawn
  • 26
  • 4
  • Ahaa!!! Now I get it! Thank you so much! So it is just return wrap, which if I want it to be executed, I would naturally call it with wrap(). One more thing I am trying to rationalize. With the same code I showed above, if I called it like this decor(print_text()), it still for some reason shows the result of print_text() before returning a function (.wrap()>). Why is that? – HKn Apr 02 '20 at 17:26
  • That is because instead of passing a function to `decor`, you are passing the value returned from `print_text()` – FoxYawn Apr 02 '20 at 17:48
  • If you want to jump in the deep end there is [function currying](https://stackoverflow.com/questions/24881604/when-should-i-use-function-currying-in-python). That's really not a huge leap from decorators. – FoxYawn Apr 02 '20 at 18:02