Search code examples
pythonclosurespython-decoratorsscoping

How to make python functions bind enclosing variable to value rather than to name


In python functions its perfectly legal to reference variables from enclosing scopes. These variables refer to whatever value they posses at the time the function is called. So, for instance

x = 1 # unused
def foo(y):
    return x+y

x=10
print(foo(1)) # prints 11
x=11
print(foo(1)) # prints 12

is it possible to make the variable x be frozen to whatever the value of x was at creation time? That is, I want the x in foo to bind to the value of the outer x rather than its name. Basically the behavior I want is

x = 1
def foo(y):
    return 1+y # I'd rather not hard code the value of x here

x=10
print(foo(1)) # prints 2
x=11
print(foo(1)) # prints 2

I currently have a working solution, but It's not very readable, I think there might be a better solution (a decorator perhaps?)

my solution

x = 1
def wrapper():
    local_x = x
    def foo(y):
        return local_x+y
    return foo
foo = wrapper()

x=10
print(foo(1)) # prints 2
x=11
print(foo(1)) # prints 2

Solution

  • Use functools.partial to apply the first argument and get a new function accepting one argument. I chose not to decorate the add function, as it is very general and useful elsewhere.

    from functools import partial
    from operator import add       # does same thing as your foo
    
    x = 1
    foo = partial(add, x)
    print(foo(1)) # prints 2
    
    x = 11
    
    print(foo(1)) # prints 2
    

    In keeping with your original solution, you could also do something like.

    def wrapper(localx):
        def foo(y):
            return localx + y
        return foo
    
    foo2 = wrapper(x)
    print(foo2(1)) # prints 12
    

    Note that now the dependency on a global is cut and you could even pass a literal value to wrapper (e.g. foo3 = wrapper(5)).