0

A similar question has been asked, but the OP compares the wrapper to a function without return, thus the answers focus on that "fault".
I understood decorators and why we use them instead of sub-classes after reading this article. There, they write the following example in which they introduce the need of the wrapper:

def uppercase_decorator(function):
    def wrapper():
        funct = function()
        make_uppercase = funct.upper()
        return make_uppercase

    return wrapper

Yet, I can write "the same thing" (I'm expecting you to say it is not), this way:

def uppercase_decorator(function): #previously 'function' was called 'message' but 'function' is clearer for comparison. 
    make_uppercase = function().upper
    return make_uppercase

Both versions could be applied to this fn, resulting in the same output ('HI ALL!') when calling salute():

@uppercase_decorator
def salute():
    return 'Hi all!'

If main fn returns a random string (thanks to @wim for suggestion) one can notice that every time it runs it, the one without wrapper always returns the same thing when running the sayGarbage() line:

def decorateIt(fn):
    toUpper = fn().upper  
    return toUpper  

def decorateW(fn):  
    def wrapper():  
        funct = fn()  
        toUpper = funct.upper()  
        return toUpper  
    return wrapper  

import random, string  

@decorateIt  
def sayGarbage():  
    return "".join(random.choice(string.ascii_lowercase) for i in range(6))  

sayGarbage()

Why is that so?

Martin
  • 414
  • 7
  • 21
  • What is `message` in your version of `uppercase_decorator` doing? Where did `func()` come from? – muru Jun 19 '19 at 19:38
  • 1
    Totally misunderstanding how decorators work. The decorator *receives the to-be-decorated function* as argument! – wim Jun 19 '19 at 19:39
  • The difference is that the second version *does not work*. – Daniel Roseman Jun 19 '19 at 19:53
  • Sorry, I missed changing 'funct' to 'message'. Now it should work – Martin Jun 19 '19 at 20:08
  • Now it "works" because you have two mistakes that cancel eachother out :) – wim Jun 19 '19 at 20:10
  • Oh, really? (ashamed...) – Martin Jun 19 '19 at 20:12
  • 1
    After the decoration, salute will now be the *method* `'Hi all!'.upper`. – wim Jun 19 '19 at 20:18
  • Do you mean this is what happens with a well defined decorator? Or do you mean that this is what is happening in my case but it shouldn't be that way? (I changed 'message' to 'function' to reflect what I think it is doing) – Martin Jun 19 '19 at 20:30
  • Oh, you mean, salute will be "just" the method, and we want to extend the functionality, not to restrict it... – Martin Jun 19 '19 at 20:39

1 Answers1

4

Let's look what happens here:

def uppercase_decorator(message): 
    make_uppercase = func().upper
    return make_uppercase

@uppercase_decorator
def salute():
    return 'Hi all!'
  • Firstly, uppercase_decorator gets defined.
  • Secondly, salute gets defined.
  • Thirdly, uppercase_decorator gets called, passing in the function object salute in the place of the first positional argument (message).

At this point, the code will crash, because func() gets called and that was not defined anywhere. If it worked in your Python REPL session, then you must have had the name func lying around in global scope from earlier, and so the code only appeared to work by coincidence.

Do we really need wrappers for decorators?

No. It is possible to write decorators in a nicer "closureless" style like you suggest. To write a decorator without the nested function, you can use the popular library decorator.py. It works like this:

from decorator import decorator

@decorator
def uppercase_decorator(func, *args, **kwargs):
    result = func(*args, **kwargs)
    return result.upper()
wim
  • 338,267
  • 99
  • 616
  • 750
  • No, I thought I had seen the difference after your comment above saying that salute will now be the method `'Hi all!'.upper`, but the wrapper version would do the same as I see it. Why would assigning `function()` to an intermediate variable (`funct`), make any difference? – Martin Jun 20 '19 at 00:31
  • Consider what happens at decoration time. In your first version, the function is *used* but it is not actually being *called*. In the second version, your function actually gets called. Perhaps, instead of `return 'Hi all!'` you should return a randomly generated string, something which will be different each time instead of just a constant value - that will show you the difference in behavior between the two versions more clearly. – wim Jun 20 '19 at 01:23
  • I've made it to return a random string and created two decorator (with and without wrapper). Both versions return a different uppercase string each time. Failed to notice any difference... (!?) : I'm updating the code in the question as I can't format it properly here... – Martin Jun 20 '19 at 16:30
  • Your example is good - using your `decorateIt`, this returns the _same_ random string everytime. Using `decorateW` returns a different random string everytime. I'm not sure how/why you would see anything different, probably some PEBKAC here. – wim Jun 20 '19 at 16:50
  • I was just running the whole code, instead of just the fn call, that's why... Now I see what you say. I also see the problem doesn't have anything to do with the intermediate variable, which is not actually needed, but only with not having the wrapper. Having it or not is what makes the code working or returning a `'str' object is not callable` exception.. So thanks for your tip, I still need to see the iceberg but, although slow, I'm getting closer to see it... – Martin Jun 21 '19 at 15:09
  • It used to return the same string, but I 'fixed' it and now it returns the mentioned exception. I'm tired and can't remember what I did 'to fix it' (it was clearly a progress though). Yet I understand that `sayGarbage()` doesn't just run the decorated function; the @decorator substitutes `sayGarbage()` for `decorator= decorateIt(sayGarbage)` and then calls `decorator()`. My mind is not clear now but I have a blurred intuition that this is what makes `sayGargage()` return 'str is not callable'. The wrapper manages to not return the result of the decorator function but a fn object, without '()' – Martin Jun 21 '19 at 15:29