pythonstylesinner-classesnested-functiongoogle-style-guide

Closing over a local value


I read the following in Google's Python styleguide:

"Avoid nested functions or classes except when closing over a local value".

What does "closing over a local value" mean?

The complete relevant section is below:

2.6 Nested/Local/Inner Classes and Functions

Nested local functions or classes are fine when used to close over a local variable. Inner classes are fine.

2.6.1 Definition

A class can be defined inside of a method, function, or class. A function can be defined inside a method or function. Nested functions have read-only access to variables defined in enclosing scopes.

2.6.2 Pros

Allows definition of utility classes and functions that are only used inside of a very limited scope. Very ADT-y. Commonly used for implementing decorators.

2.6.3 Cons

Instances of nested or local classes cannot be pickled. Nested functions and classes cannot be directly tested. Nesting can make your outer function longer and less readable.

2.6.4 Decision

They are fine with some caveats. Avoid nested functions or classes except when closing over a local value. Do not nest a function just to hide it from users of a module. Instead, prefix its name with an _ at the module level so that it can still be accessed by tests.


Solution

  • It means unless you create a closure. A closure is when a free variable refers to a variable in an enclosing scope. So for example:

    def foo():
        bar = 42
        def baz():
            print(bar)
        return baz
    foo()()
    

    This will print 42, because baz is a closure over the variable bar in the local scope of foo. Note, you can even introspect this:

    f = foo()
    print(f.__closure__)
    

    So, essentially the guide is telling you to use a nested function only when it is actually useful for something, a small contrived example could be a function factory:

    def make_adder(n):
        def add(x):
            return n + x
        return add
    
    add2 = make_adder(2)
    add5 = make_adder(5)
    
    print(add2(1), add5(1))
    

    add2 and add5 are closures over n.

    Some people might want to nest a function merely to hide it from the global scope, something like:

    def foo(n):
    
        def frobnicate(x, y):
            return x + y
    
        m = n + 42
        return frobnicate(m, 11)
    

    The style guide is saying don't do that, just do:

    def frobnicate(x, y):
        return x + y
    
    def foo(n):
        m = n + 42
        return frobnicate(m, 11)