4

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.

Josh
  • 11,979
  • 17
  • 60
  • 96
  • If I put `"closing over a value"` (including quotation marks) into a search engine, I get multiple relevant hits at the top. – Karl Knechtel May 16 '20 at 03:17

1 Answers1

6

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)
juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172
  • An in between case is where the nested function uses the local variable(s), and is also run within the function, rather than being returned. I can think of examples of this in the `argparse` module. – hpaulj May 16 '20 at 14:58
  • @hpaulj Why is that an in-between case? (following strictly what the styleguide says) – Josh May 16 '20 at 16:29
  • What about if you have a function 'do_something', and an inner recursive function 'recursive_helper'. Would that be accepted? Pros: you wouldn't have to pass variables to the recursive function (imagine having a big variable to pass, it could be inefficient), since it could access those in the scope of the containing function. Cons: might be difficult to test the helper function. – Nicola Amadio Nov 26 '20 at 12:16
  • @NicolaAmadio "imagine having a big variable to pass, it could be inefficient" well, passing an object in Python never copies the object, so regardless of the object you pass it doesn't affect efficiency. But yeah sure, you could use the closure to make the recursive call cleaner – juanpa.arrivillaga Nov 26 '20 at 18:58
  • @juanpa.arrivillaga you're right, it's only passing the reference. I don't know if you could call it a closure? What I mean is this: ``` def main_func(): def helper(obj): if something: helper(obj) helper(data) ``` – Nicola Amadio Nov 28 '20 at 15:39