3

When I studied "naming and binding" in Python, I saw the following example:

>>> def testClosure(maxIndex):
        def closureTest(maxIndex=maxIndex):
            return maxIndex
        maxIndex += 5
        return closureTest()
>>> print(testClosure(10))
10


>>> def testClosure(maxIndex):
        def closureTest():
            return maxIndex
       maxIndex += 5
       return closureTest()
>>> print(testClosure(10))
15

The author explained it as: In the latter function, free variable in an inner scope bind to variable in the outer scope, not objects.

Then my question is: What's the difference between "bind to variable" and "bind to object" in Python?

Also, it's very tricky:the result is different if I re-arrange the code.

>>> def testClosure(maxIndex):
        maxIndex += 5
        def closureTest(maxIndex=maxIndex):
            return maxIndex
        return closureTest()

>>> print(testClosure(10))
15

Thanks in advance.

unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
lostinmoney
  • 256
  • 1
  • 11
  • The original post is: http://bytes.com/topic/python/answers/578571-when-closure-get-external-variables-value – lostinmoney Dec 09 '10 at 09:35
  • I think the answers from both unutbu and Jim Dennis hit the point. But I, in my humble opinion, feel that Jim's answer explains the dynamic characteristic of Python more clearly. Then I choose Jim's answer. – lostinmoney Dec 10 '10 at 07:49

3 Answers3

8

Two key facts:

  1. Python uses the LEGB rule to look up the value of (bare) variables names. LEGB stands for Local, Extended, Global, Builtins. That means a variable name "binds" to the local value, and if there is none, then the value is looked-up in the extended scope, and if there is no such variable, lookup is done in the global scope, and finally in the builtins scope.
  2. When defining a function like

    def closureTest(maxIndex=maxIndex):
        return maxIndex
    

    default values are fixed at definition-time, not run-time. By definition-time I mean the time when the def statement is processed -- when the function is defined. By run-time I mean the time when the function is called. Note that when you have nested functions, the inner function's definition-time occurs after the outer function has been called.


The first example is made more complicated by the fact that the variable name maxIndex is overused. You'll understand the first example if you first understand this:

>>> def testClosure(maxIndex):              
        def closureTest(index=maxIndex):     # (1)
            return index                     
        maxIndex += 5
        return closureTest()                 # (2)
>>> print(testClosure(10))
  • (1) At definition-time, index's default value is set to 10.
  • (2) When closureTest() is called with no arguments, index is set to the default value 10. So this is the value returned.

def testClosure(maxIndex):
    def closureTest():
        return maxIndex                 # (3)
   maxIndex += 5
   return closureTest()                 # (4)
print(testClosure(10))
  • (3) The LEGB rule tells Python to lookup the value of maxIndex in the local scope. There is no maxIndex defined in the local scope, so it looks in the extended scope. It finds the maxIndex which is an argument to testClosure.

  • (4) By the time closureTest() is called, maxIndex has the value 15. So the maxIndex returned by closureTest() is 15.


>>> def testClosure(maxIndex):
        maxIndex += 5                           # (5)    
        def closureTest(maxIndex=maxIndex):     # (6)
            return maxIndex
        return closureTest()                    # (7)
  • (5) maxIndex is 15

  • (6) closureTest's maxIndex is set to have default value 15 at definition-time.

  • (7) When closureTest() is called with no arguments, the default value for maxIndex is used. The value 15 is returned.

Community
  • 1
  • 1
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • Good explanation of variable scoping and binding but doesn't answer OP's question which is 'What's the difference between "bind to variable" and "bind to object" in Python?'. – martineau Dec 09 '10 at 10:28
  • 2
    @martineau: I find Python code clear, but words are sometimes unclear. I don't really understand the words "bind to variable" versus "bind to object" as used in http://bytes.com/topic/python/answers/578571-when-closure-get-external-variables-value. So I tried to give the OP a way to understand the code without resorting to either phrase. – unutbu Dec 09 '10 at 10:45
  • I think the phrase "bind to variable" has no formal application to Python. I think whoever used it caused this poster confusion rather than explaining how the name binding in Python works as functions are defined (which is actually at run-time, contrary to what unutbu is saying here). – Jim Dennis Dec 09 '10 at 10:55
  • @unutbu (and @Jim Dennis): That being the case, I think both of you should have addressed the issue directly in your answers -- otherwise you run the risk of it appearing as though either you didn't read the OP's question or are just ignoring their stated question. FWIW, the terms "bind to variable" vs "bind to object" were first mentioned in a response from Fredrik Lundh, an extremely experienced and well respected member of the Python community, so it might be worth *your* while to spend some (more) time pondering what he meant and reading all the posts from the link. – martineau Dec 09 '10 at 17:19
  • 1
    @unutbu: Many thanks for your detail explanation, it's really helpful. You give comments step by step, much appreciate! – lostinmoney Dec 10 '10 at 07:44
  • @martineau: I have read Lundh's comment and I stand by what I said. The phrases "bind to variable" and "bind to object" are not clarifying the semantics and seem to be outright confusing to a significant number of readers. The (variable) names in parameter lists are local to the function's scope and are mapped to arguments at call time. Values assigned in the functions definition are evaluated at the time the 'def' statement is executed. It's simple and hinges on the distinction between "parameter" and "argument." – Jim Dennis Dec 21 '10 at 19:35
  • @Jim Dennis: Yes, I understand the differences. All I was saying was that if you found the posts and/or terminology used in them confusing -- I know I did -- that I thought you should have at least mentioned that in your answers because it appeared to me that you were ignoring the question directly asked by the OP...and even though he's apparently fine with your answers, that's not necessarily going to hold for everyone reading them. – martineau Dec 21 '10 at 21:18
  • Good explaination with brief examples – Praneeth Jun 12 '15 at 19:54
3

It might be less confusing if you think of the binding that's happening in the parameter expression of the 'def' statement. When you see 'def closureTest(maxIndex=maxIndex):' that is a statement like 'if' or 'while' which is followed by a suite of code to be parsed and bound to the function (callable object).

The 'def' statement is evaluated in the scope where it's found (conceptually at the same level of nesting/indentation). Its parameter expression declares how arguments will be mapped to names within the function's own scope. Any of those which provide a default (such as maxIndex in your examples) create a function object with the corresponding parameter name bound to whatever object was named or instantiated at the time (within the scope of) the 'def' statement.

When the function is called each of its parameters (names within its scope)is bound to any arguments supplied to the function. Any optional parameters are thus left bound to whichever arguments were evaluated as part of the 'def' statement.

In all of your examples an inner function is created during each invocation of the outer function. In your second example the parameter list is empty and the inner function is simply seeing the name through one level of nested scope. In the first example the inner function's def statement creates a default maxIndex name within the new function's namespace (thus preventing any resolution of the name with values from the surrounding scope, as you'd expect for any local variable within any function).

In the last example the value of maxIndex is modified before the inner function is (re)-defined. When you realize the the function is being (re)-defined on each outer function invocation then it shouldn't seem so tricky.

Python is a dynamic language. 'def' is a statement is being executed by the VM every time the flow of control passes through that line of your code. (Yes, the code has been byte compiled, but 'def' is compiled into VM op codes which perform code evaluation and name binding (to the function's name) at run-time).

If you define a function with a parameter list like '(myList=list())' then a list will be instantiated as the definition is executed. It will be accessible from within invocations of the functions code any time the function is called with no arguments. Any invocation with an argument will be executed with that parameter name bound to the argument supplied at invocation. (The object instantiated at def time is still referenced by the code object that was defined -- the suite indented after the def statement).

None of this will make any sense if you don't keep the distinction between parameters and arguments. Remember that parameters are part of the function's definition; they define how arguments will be mapped into the function's local namespace. Arguments are part of the invocation; they are the things being passed into any call of the function.

I hope this helps. I realize that the distinction is subtle and the terms are very frequently mis-used as though they were interchangeable (including throughout the Python documentation).

Jim Dennis
  • 17,054
  • 13
  • 68
  • 116
  • I want to add that the parameter list like 'maxIndex= maxIndex' looks confusing to new Pythonistas because a parameter name is being evaluated by the def statement to be bound to the object which is bound to the same name in the scope that the def statement is being executed. If you imagine the parameter names to be like string literals (keys in the function's namespace) it may help. Names and expressions to the right of and = in these parameter lists are evaluated like any other expression in Python at the time the definition is executed. – Jim Dennis Dec 09 '10 at 11:02
0
>>> def testClosure(maxIndex):
        def closureTest(maxIndex=maxIndex): # You're creating a function with a kwarg of maxIndex
            return maxIndex                 # which references the int passed in from outside
        maxIndex += 5                       # Incrementing maxIndex means that the maxIndex in the outer
        return closureTest()                # scope now points to a different int.
>>> print(testClosure(10))
10

Consider:

>>> a = b = 1
>>> a += 1
>>> a
2
>>> b
1

I don't know what you mean by "bind to the object" and "bind to variable". "Variables" are references to things, when you increment a, you're modifying it to refer to a different value, b still refers the to original value.

When you're not passing in the value of maxIndex into your inner function and then ask for it, as it's not defined in the local scope, it's looked for in the extended scope.

I'm making a realively large assumption here, but you can see a difference in the time it takes to execute, which I'd be inclined to attribute to the cost of this extended lookup:

>>> import timeit
>>> def testClosure(maxIndex):
...     def closureTest(maxIndex=maxIndex):
...         return maxIndex
...     maxIndex += 5
...     return closureTest()
...
>>> def testClosure2(maxIndex):
...     def closureTest():
...         return maxIndex
...     maxIndex += 5
...     return closureTest()
...
>>> timeit.Timer('testClosure(10)','from __main__ import testClosure').timeit()
1.4626929759979248
>>> timeit.Timer('testClosure2(10)','from __main__ import testClosure2').timeit()
1.7869210243225098
MattH
  • 37,273
  • 11
  • 82
  • 84