Zion
Zion

Reputation: 1610

closure behavior (python)

I am viewing python epiphanies by O'Reilly media and they gave this as an example

def log(severity,message):
    print("{0}:{1}".format(severity.upper(),message))

def create_logger(severity):
    def logger(message):
        log(severity,message)
    return logger

warning2 = create_logger("warning")
warning2("this is a warning")

output:

WARNING:this is a warning

I knew bit about decorators and the format of create_logger is exactly like a decorator except it's not being used as one.

what boggles my mind is this line warning2("this is a warning")

warning2 contains the create_logger function with an argument of "warning" so it's basically getting called already

and doing warning2("this is a warning") is like calling it twice?

I did however notice that doing:

create_logger("warning")("this is a warning")

yields the same results which would lead me to believe that the second call i.e ("this is a warning") is implicitly referencing the logger function inside create_logger and passing the arguments to it?

am I understanding it right? and when would we ever need to use this?

Upvotes: 0

Views: 42

Answers (2)

Makoto
Makoto

Reputation: 106410

I'm going to borrow some words from Simeon Franklin on closures (and, by extension, decorators):

Python supports a feature called function closures which means that inner functions defined in non-global scope remember what their enclosing namespaces looked like at definition time.

So yes, this is a closure. Decorators make use of closures, but you don't have to have a decorator to use a closure.

To that, let's take a deeper look at the second part of what was said: the inner function defined in non-global space remembers what its inclosing namespace was at definition time.

At definition time, here's what it would have looked like.

def logger(message):
    log("warning",message)
return logger

Since we pass "warning" in to severity at the outer level, its value is bound to "warning" inside of the closure.

At this point, all we've done is define a method that takes a parameter of message, and we are referencing it through warning2.

We then invoke this method via warning2("this is a warning").

This is also why your currying operation works: create_logger("warning")("this is a warning") does the exact same thing as the above code, but we're simply not keeping track of the creation of the function with "warning" bound as its severity anywhere.

Why is this useful? This gives you the ability to write functions that create functions, or give you the ability to deduplicate a lot of code. Imagine if you had to make use of a logger that required you to supply its severity level and a message, when ideally, all you'd need to supply is its message, and you would pick the logger function most suited for the task. This particular snippet of code would enable you to do that.

Upvotes: 1

BrenBarn
BrenBarn

Reputation: 251373

create_logger is a function whose return value is another function. When you call create_logger, the function logger defined inside it is not called; it is just returned. So when you do warning2 = create_logger('warning'), warning2 is a function. warning2("this is a warning") then calls it as normal. The second call does not "implicitly" reference the inner function; the first call returns an explicit reference to the inner function.

As for why you would want to use this, it can be quite powerful. The ability to define functions inside other functions allows you to create a sort of "skeleton" inner function that can provide an outline of some task, and then you can later flesh it out with arguments passed to the outer function. In this case logger is the "skeleton" whose "flesh" is provided by the call to create_logger. Decorators, which you mentioned, are one of the most prominent uses of nested functions.

Upvotes: 1

Related Questions