Difficulties with closures to construct a logging

Screen Link:
https://app.dataquest.io/m/265/building-a-pipeline-class/4/python-decorators

Hello,

I’m having difficulties understanding closures for constructing a logging.
What I don’t understand is how arguments are passed to inner function.

My Code:

def add(a, b):
    return a + b

def logger(func):
    def inner(*args):
        print('Calling function: {}'.format(func.__name__))
        print('With args: {}'.format(args))
        return func(*args)
    return inner

logged_add = logger(add)  --> passing the add function to logger function. 
print(logged_add(1, 2)) --> Where do 1 and 2 get passed to inner function? 

Appreciate your help very much

1 Like

Hey, Luuk.

That piece of code does the same as this:

def add(a, b):
    return a + b

def logger(func):
    def inner(x,y):
        print('Calling function: {}'.format(func.__name__))
        print('With args: {}'.format((x,y)))
        return func(x,y)
    return inner

The *args thing is something that has nothing to do with decorators. This exists to accommodate for having a function with a variable number of positional arguments.

Let’s see an example. Take the sum function. It takes one positional argument only, an iterable (something like a list).

>>> sum([1,2,3])
6

If you try passing the arguments directly, it causes an error:

>>> sum(1,2,3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: sum expected at most 2 arguments, got 3

Let’s create a function, called new_sum, that accepts as many positional arguments as we want:

>>> def new_sum(*args): return sum(args)
... 
>>> new_sum(1,2,3)
6

You can read more about this in the Arbitrary Argument Lists section of the official documentation.

I hope this helps.

Hi Bruno, I think that the LuukvanVliet question was not about the *operator but how the arguments get to the inner function, no matter if it is one or thousands arguments.
I have the same doubt and don’t know if it is hard for me to understand the syntax.
I would appreciate any help.

Hello, Gustavo.

Yeah, I agree. I probably misunderstood the question.

I think it’s useful to take a step back and look at this with fresh eyes.

The logger function returns functions. Typically, a function returns other kinds of objects, like strings or numbers. This function, however, returns a function.

What function does it return? It returns what we are here calling the inner function. At this point, it doesn’t really matter what inner does. All that matters is that logger, when properly called, returns a function.

So it’s not that the arguments are passed to the inner function per se, but rather that what logger returns when it is properly called is a function (to repeat myself).

The logger function, after it is called, has no influence on what it returns anymore:

>>> def logger(func):
...     def inner(*args):
...         print('Calling function: {}'.format(func.__name__))
...         print('With args: {}'.format(args))
...         return func(*args)
...     return inner
... 
>>> logged_add = logger(add) 
>>> del logger # We delete `logger`
>>> logger # We verify it doesn't exist anymore
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'logger' is not defined
>>> type(logged_add) # And we verify that `logged_add` continues to exist
<class 'function'>

I realize this doesn’t directly answer your question, but hopefully it sheds enough light on what surrounds that you can see it clearly now. Let me know if you have additional questions.