Python closures and strange behaviors

A nested function can access (ro by default) the variables of the enclosing scope. To modify them, the keyword nonlocal is required.

The error below is due to 'late binding' and makes me wonder why one would use this. Supposedly, it prevents the need for a global variable. Personally, I would rather use a class. An exception is if one is writing decorators.

This is not caused by the lambda. A nested (named) function inside of mul() would have the same problem.


In [73]:
def mul_broke(count):
    return [lambda x: x*i for i in range(count)]

print 'Expected [0, 2, 4, 6]'
print 'Actual  ', [m(2) for m in mul_broke(4)]


Expected [0, 2, 4, 6]
Actual   [6, 6, 6, 6]

In [70]:
def mul(count):
    '''
    Due to Python’s aforementioned behavior concerning evaluating
    default arguments to functions, you can create a closure that
    binds immediately to its arguments by using a default arg.
    '''
    return [lambda x, i=i: x*i for i in range(count)]

print 'Expected [0, 2, 4, 6]'
print 'Actual  ', [m(2) for m in mul(4)]


Expected [0, 2, 4, 6]
Actual   [0, 2, 4, 6]

More about closures


In [ ]:
def print_msg(msg):
    def printer():
        print(msg)  # Has access to outer msg
    return printer  # Interesting!

In [54]:
print_msg('First')()


First

Lets bind a variable another to the returned function printer(). Strangely, it remembered the value of msg. This is a closure.


In [55]:
another = print_msg('Second')
another()


Second

It does not matter if msg goes out of scope or the function is deleted.


In [56]:
del print_msg
another()


Second

As seen from the above example, we have a closure in Python when a nested function references a value in its enclosing scope. The criteria that must be met to create closure in Python are summarized in the following points.

  • We must have a nested function (function inside a function). A lambda is a function, although anonymous.
  • The nested function must refer to a value defined in the enclosing function.
  • The enclosing function must return the nested function.

Python class inheritance

Python class member variables are stored in a dict. If a subclass' member variable is not found in this dict, the parent class dict is searched. The child inherits the state of its parent. This is very different from C++.


In [19]:
class P(object):
    x = 1
    
class A(P):
    pass

class B(P):
    pass

print 'P A B\n-----'
print P.x, A.x, B.x
A.x = 2
print P.x, A.x, B.x, '<-- A gets its own x via assignment'
P.x = 3
print P.x, A.x, B.x, '<-- B is still sharing Ps x'


P A B
-----
1 1 1
1 2 1 <-- A gets its own x via assignment
3 2 3 <-- B is still sharing Ps x

Never use empty containers as default parameters

The [ ] in bar(x, [ ]) is created when the function is defined. It is continues to exist and is reused (even if modified) with each call of bar() that relies on the default my_list.


In [47]:
def foo(x, my_list=None):
    if my_list is None:
        my_list = []
    my_list.append(x)
    return my_list
    
print 'foo (correct)\n-------------'
print foo('s'), foo('t'), foo('u')

def bar(x, my_list=[]):
    my_list.append(x)
    return my_list
 
print '\nbar (broken)\n------------'
print bar('s'), bar('t'), bar('u')


foo (correct)
-------------
['s'] ['t'] ['u']

bar (broken)
------------
['s'] ['s', 't'] ['s', 't', 'u']

Fun with lists


In [33]:
import pprint
L = [[]] * 5  # Creates a list of 5 references to the same [].
print L
L[0].append(10) # Append to the first, but they are all the same thing.
print L
L[1].append(20) # Append to the second, but still the same.
print L
L.append(30)  # Append to the outer list.
print L


[[], [], [], [], []]
[[10], [10], [10], [10], [10]]
[[10, 20], [10, 20], [10, 20], [10, 20], [10, 20]]
[[10, 20], [10, 20], [10, 20], [10, 20], [10, 20], 30]

In one line of code, find the even numbers in even indices


In [49]:
L = [1,2,4,5,6,8,-2]
#    0,1,2,3,4,5,6
# Soln 1 with enumerate
L_enum = [i for n,i in enumerate(L) if (i%2 == 0 and n%2 == 0)]
# Soln 2 with slicing
L_sliced = [i for i in L[::2] if i%2 == 0]
print L, '-->', L_enum
print L, '-->', L_sliced


[1, 2, 4, 5, 6, 8, -2] --> [4, 6, -2]
[1, 2, 4, 5, 6, 8, -2] --> [4, 6, -2]

An implementation oddity

In cpython, numbers <= 255 are cached objects


In [42]:
a = 255
b = 255
print a is b
c = 512
d = 512
print c is d


True
False

List assignment is a reference


In [44]:
i = [1,2,3]
j = list(i)
k = i
i.append(4)
print 'i', i
print 'j', j
print 'k', k


i [1, 2, 3, 4]
j [1, 2, 3]
k [1, 2, 3, 4]

String spaces --> %20s using RE sub()


In [46]:
import re
import unittest

def space20(strg):
    '''Replace " " with "%20"'''
    space_to_20 = re.compile(r' ')
    return space_to_20.sub(r'%20', strg)

class Space20Test(unittest.TestCase):
    def test_zero_len(self):
        self.failUnlessEqual('', space20(''))
    def test_match_nothing(self):
        self.failUnlessEqual('abc;def', space20('abc;def'))
    def test_match_at_start(self):
        self.failUnlessEqual('%20abc', space20(' abc'))
    def test_match_in_middle(self):
        self.failUnlessEqual('abc%20def', space20('abc def'))
    def test_match_at_end(self):
        self.failUnlessEqual('abc%20', space20('abc '))
    def test_match_complex(self):
        self.failUnlessEqual('%20%20abc%20%20def%20%20',
                             space20('  abc  def  '))
            
squares_suite = unittest.TestLoader().loadTestsFromTestCase(Space20Test)
unittest.TextTestRunner().run(squares_suite)


......
----------------------------------------------------------------------
Ran 6 tests in 0.003s

OK
Out[46]:
<unittest.runner.TextTestResult run=6 errors=0 failures=0>