Reputation: 1610
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
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
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