How do I capture the CURRENT values of closure variables immutably in Python?

Viewed 634

If I do

def f():
    a = 1
    g = lambda: a
    a = 2
    return g
print(f()())

The value that is printed is 2, because a is mutated after g is constructed.
How do I get g to capture the value of a statically so that later modifications are ignored?

2 Answers

In python3.8 the CellType class was added to types, which means you can create a function with a custom closure. That allows us to write a function that converts functions with closures that reference a parent frame to closures with static values:

from types import FunctionType, CellType

def capture(f):
    """ Returns a copy of the given function with its closure values and globals shallow-copied """
    closure = tuple(CellType(cell.cell_contents) for cell in f.__closure__)
    return FunctionType(f.__code__, f.__globals__.copy(), f.__name__, f.__defaults__, closure)

print([f() for f in [        lambda: i  for i in range(5)]]) # Outputs [4, 4, 4, 4, 4]
print([f() for f in [capture(lambda: i) for i in range(5)]]) # Outputs [0, 1, 2, 3, 4]

The capture function could be tweaked in a few ways; The current implementation captures all globals and free vars, and does so shallowly. You could decide to deepcopy the captured values, to capture only the free vars, to capture only specific variable names according to an argument, etc.

Related