How to access a function inside a function?
You really don't want to go down this rabbit hole, but if you insist, it is possible. With some work.
The nested function is created anew for each call to make_adder()
:
>>> import dis
>>> dis.dis(make_adder)
2 0 LOAD_CLOSURE 0 (x)
3 BUILD_TUPLE 1
6 LOAD_CONST 1 (<code object adder at 0x10fc988b0, file "<stdin>", line 2>)
9 MAKE_CLOSURE 0
12 STORE_FAST 1 (adder)
4 15 LOAD_FAST 1 (adder)
18 RETURN_VALUE
The MAKE_CLOSURE
opcode there creates a function with a closure, a nested function referring to x
from the parent function (the LOAD_CLOSURE
opcode builds the closure cell for the function).
Without calling the make_adder
function, you can only access the code object; it is stored as a constant with the make_adder()
function code. The byte code for adder
counts on being able to access the x
variable as a scoped cell, however, which makes the code object almost useless to you:
>>> make_adder.__code__.co_consts
(None, <code object adder at 0x10fc988b0, file "<stdin>", line 2>)
>>> dis.dis(make_adder.__code__.co_consts[1])
3 0 LOAD_DEREF 0 (x)
3 LOAD_FAST 0 (y)
6 BINARY_ADD
7 RETURN_VALUE
LOAD_DEREF
loads a value from a closure cell. To make the code object into a function object again, you'd have to pass that to the function constructor:
>>> from types import FunctionType
>>> FunctionType(make_adder.__code__.co_consts[1], globals(),
... None, None, (5,))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: arg 5 (closure) expected cell, found int
but as you can see, the constructor expects to find a closure, not an integer value. To create a closure, we need, well, a function that has free variables; those marked by the compiler as available for closing over. And it needs to return those closed over values to us, it is not possible to create a closure otherwise. Thus, we create a nested function just for creating a closure:
def make_closure_cell(val):
def nested():
return val
return nested.__closure__[0]
cell = make_closure_cell(5)
Now we can recreate adder()
without calling make_adder
:
>>> adder = FunctionType(make_adder.__code__.co_consts[1], globals(),
... None, None, (cell,))
>>> adder(10)
15
Perhaps just calling make_adder()
would have been simpler.
Incidentally, as you can see, functions are first-class objects in Python. make_adder
is an object, and by adding (somearguments)
you invoke, or call the function. In this case, that function returns another function object, one that you can call as well. In the above tortuous example of how to create adder()
without calling make_adder()
, I referred to the make_adder
function object without calling it; to disassemble the Python byte code attached to it, or to retrieve constants or closures from it, for example. In the same way, the make_adder()
function returns the adder
function object; the point of make_adder()
is to create that function for something else to later call it.
The above session was conducted with compatibility between Python 2 and 3 in mind. Older Python 2 versions work the same way, albeit that some of the details differ a little; some attributes have different names, such as func_code
instead of __code__
, for example. Look up the documentation on these in the inspect
module and the Python datamodel if you want to know the nitty gritty details.
No, you can't call it directly as it is a local variable to make_adder
.
You need to use adder()
because return adder
returned the function object adder
when you called make_adder(5)
. To execute this function object you need ()
def make_adder(x):
def adder(y):
return x+y
return adder
...
>>> make_adder(5) #returns the function object adder
<function adder at 0x9fefa74>
Here you can call it directly because you've access to it, as it was returned by the function make_adder
. The returned object is actually called a closure because even though the function make_addr
has already returned, the function object adder
returned by it can still access the variable x
. In py3.x you can also modify the value of x
using nonlocal
statement.
>>> make_adder(5)(10)
15
Py3.x example:
>>> def make_addr(x):
def adder(y):
nonlocal x
x += 1
return x+y
return adder
...
>>> f = make_addr(5)
>>> f(5) #with each call x gets incremented
11
>>> f(5)
12
#g gets it's own closure, it is not related to f anyhow. i.e each call to
# make_addr returns a new closure.
>>> g = make_addr(5)
>>> g(5)
11
>>> g(6)
13
You are returning the function adder
to the caller, not the result of calling it, hence the absence of parentheses.
Because make_adder
returns adder
, you already have direct access to adder
. In fact, a(10)
is actually a call to adder(10)
.