Two considerations for Python closures are recommended by of

  • 2020-05-27 06:02:30
  • OfStack

What are closures ?

Simply put, closures are about getting different results based on different configuration information.

Now for the technical explanation: a closure (Closure) is short for a lexical closure (Lexical Closure) and is a function that refers to a free variable. The referenced free variable will exist with the function 1, even if it has left the environment in which it was created. Therefore, there is another view that a closure is an entity composed of a function and its associated reference environment.

Late binding

The external free variables referenced by the Python closure function are deferred bound.

Python


In [2]: def multipliers():
  ...:   return [lambda x: i * x for i in range(4)] 
In [3]: print [m(2) for m in multipliers()]
[6, 6, 6, 6]
In [2]: def multipliers():
  ...:   return [lambda x: i * x for i in range(4)] 
In [3]: print [m(2) for m in multipliers()]
[6, 6, 6, 6]

As shown in the above code: i is a free variable in the outer scope of the closure function reference. The value of the variable i is only searched when the inner function is called. Since the loop has ended and i points to the final value 3, all function calls get the same result.

Solutions:

1) bind immediately when generating closure functions (using the default value of function parameters):

Python


In [5]: def multipliers():
  return [lambda x, i=i: i* x for i in range(4)]
    ...: 
In [6]: print [m(2) for m in multipliers()]
[0, 2, 4, 6]
In [5]: def multipliers():
  return [lambda x, i=i: i* x for i in range(4)]
    ...: 
In [6]: print [m(2) for m in multipliers()]
[0, 2, 4, 6]

Such as the production of the above code: closure function, you can see every closure function parameters with default value: 1 i = i, at this point, the interpreter will find i values, and give them parameters i, so in the generated closure function outside of the scope (that is, external loop), found the variable i, hence gives its current value to the parameter i.

2) use functools. partial:

Python


In [26]: def multipliers():
  return [functools.partial(lambda i, x: x * i, i) for i in range(4)]
  ....: 
In [27]: print [m(2) for m in multipliers()]
  [0, 2, 4, 6]
In [26]: def multipliers():
  return [functools.partial(lambda i, x: x * i, i) for i in range(4)]
  ....: 
In [27]: print [m(2) for m in multipliers()]
  [0, 2, 4, 6]

Such as the above code: in case of problems due to delayed binding, you can construct partial functions through functools.partial, so that free variables are bound to closure functions first.

It is forbidden to rebind a referenced free variable within a closure function

Python


def foo(func):
  free_value = 8
  def _wrapper(*args, **kwargs):
    old_free_value = free_value # Keep the old free_value
    free_value = old_free_value * 2 # Simulation generates new free_value
    func(*args, **kwargs)
    free_value = old_free_value
  return _wrapper
def foo(func):
  free_value = 8
  def _wrapper(*args, **kwargs):
    old_free_value = free_value # Keep the old free_value
    free_value = old_free_value * 2 # Simulation generates new free_value
    func(*args, **kwargs)
    free_value = old_free_value
  return _wrapper

The above code will report an error, UnboundLocalError: local variable 'free_value' before assignment, the above code is intended to implement a decorator with a certain initialization state (free_value), but the new state can be changed as needed when the inner closure function is executed (free_value = old_free_value * 2). However, due to internal re-binding, The interpreter will treat free_value as a local variable, and old_free_value = free_value will report an error, because the interpreter thinks free_value is referenced without an assignment.

Solution:

When you want to modify the free variable referenced by the closure function, you can put it into an list, so that free_value = [8], free_value cannot be modified, but free_value[0] can be safely modified.

In addition, Python 3.x adds the nonlocal keyword, which can also solve this problem.


Related articles: