5

I'm trying to conquer one of the final basic python features that I've avoided using since I started: decorators. I'm not grocking it like i did with list-comps, and I do not understand how an inner function within a decorator declaration works.

Here's an example of what I mean. Given this chunk-o-code:

def outer(func):
    def inner(*args, **kwargs):
        print('Hi my name is ')
        return func(*args, **kwargs)
    return inner

@outer
def decorated(name):
    print(name)


decorated('Bob')

I understand that this this will print

Hi my name is
Bob

but what I don't understand is how inner obtains any *args or **kwargs from decorated()

My understanding is that

@outer
def decorated(name):
    print(name)
decorated("Bob")

is equivalent to outer(decorated("Bob")). If this is the case, how would inner() be able to access the name argument? Syntax issues aside, I'd expect the declaration for inner to look like def inner(func.args, func.kwargs):

What's going on here? What am I misunderstanding?

Rob Truxal
  • 5,856
  • 4
  • 22
  • 39

1 Answers1

5

My understanding is that

@outter
def decorated(name):
    print(name)
decorated("Bob")

is equivalent to outter(decorated("Bob")).

That’s wrong. It’s equivalent to outter(decorated)("Bob"). decorated is replaced with the return value of outer(decorated) (which is inner). When you call decorated, you’re actually calling inner.

You can check that interactively:

>>> def outter(func):
...     def inner(*args, **kwargs):
...         print('Hi my name is ')
...         return func(*args, **kwargs)
...     return inner
...
>>> @outter
... def decorated(name):
...     print(name)
...
>>> decorated
<function outter.<locals>.inner at 0x7f5aec3461e0>
Jonas Schäfer
  • 20,140
  • 5
  • 55
  • 69
  • Thank you for interactive check tip. I explained decorators (and HOFs) many times but never used interactive python for that. – Alex Yu Mar 07 '19 at 22:19
  • 1
    @AlexYu: best practice is to use [`@functool.wraps`](https://docs.python.org/3/library/functools.html#functools.wraps) to copy things like the name, docstring and other metadata and attributes over to the replacement wrapper function, in which case the `decorated` reference will have almost exactly the same `repr()` output, which is why you can't simply echo the decorated function object in real-life cases. A poor substitute is to echo `decorated.__code__`, which only includes the name `inner`, not the full `__qualname__`. – Martijn Pieters Mar 08 '19 at 11:13
  • @MartijnPieters I'm aware about best practices. But when I explain something to somebody: at first goes explanation of inner work and only after that cames "best practices" and "why they are the best" and "are they really the best" – Alex Yu Mar 08 '19 at 11:39
  • 1
    @AlexYu: that's good, but what I mean is that a lot of people encounter decorators in a framework or the standard library, at which point Jonas' illustration here will fall flat. It is one of the reasons you don't see much interactive demonstrations. – Martijn Pieters Mar 08 '19 at 11:45
  • @MartijnPieters OK. You are absolutely correct when it cames to writing actual code. But before one will be able to write code - they must be teached how to do it. And that's where interactive explorations are great – Alex Yu Mar 08 '19 at 11:51
  • Thank you! this clarifies the arguments bit I am still confused about the ```inner``` inheriting the actual functions? There are some examples I have see of decorators with arguments that don't explicitly specify the function that ```inner``` inherits? – catic_ Apr 26 '22 at 08:21