Higher-order functions
Functions like makeInc
that in turn, return another function are called higher order functions. Usually, functions are known to accept data as input and return data as output. With higher order functions, functions instead of data, either return code as output or accept code as input. This code is wrapped into a function. In Python, functions are first class citizens which means functions, just like data, can be passed around. For instance:
myvariable = print
Notice, how I have assigned print
to myvariable
and how I have dropped the parentheses after print
Functions without parentheses are called function objects. This means myvariable
now is just another name for print
:
print("Hello World!")
myvariable("Hello World!")
Both of the above statements do the exact same thing. What can be assigned to variables can also be returned from functions:
def myfunction():
return print
myfunction()("Hello World!");
Now let's look at your example:
def makeInc(x):
def inc(y):
return y + x
return inc
makeInc
is a function that accepts a parameter called x
. It then defines another nested inner function called inc
which takes in a parameter called y
. The thing about nested functions is that they have access to the variables of the enclosing function as well. Here, inc
is the inner function but it has access to x
which is a variable of the enclosing outer scope.
The last statement return inc
returns the inner function to the caller of makeInc
. What makeInc
essentially is doing, is creating a custom function based on the parameter it receives.
For instance:
x = makeInc(10)
makeInc
will first accept 10 and then return a function that takes in an argument y
and it increments y
by 10.
Here, x
is a function that takes in any argument y
and then increments it by 10:
x(42) # Returns 52
nonlocal
However, there is a caveat when using nested functions:
def outer():
x = 10
def inner():
x = 20
inner()
print(x) # prints 10
Here, you would assume that the last print
statement will print 20. But no! When you assign x = 20
in the inner function, it creates a new local variable called x
which is initialized to 20. The outer x
remains untouched. To modify the outer x
, use the nonlocal
keyword:
def outer():
x = 10
def inner():
nonlocal x = 20
inner()
print(x) # prints 20
If you are directly reading x
inside inner()
instead of assigning to it, you do not need nonlocal
.