Search code examples
pythonpython-3.xfunctionmetaprogrammingmutability

How to freeze mutable default parameter values?


Python chose to follow the design decision which evaluates the function's signature only once. So this code:

def test(x=[]):
    x.append(5)
    return x

print(test())
print(test())

Prints:

[5]

[5, 5]

I know it is possible to use None as a default value, and to change the parameter value inside the function if it is None.


How can I write some code that change the behavior of test, in a way that the default parameter would be reevaluated every time I call the function? The solution can include decorators and use classes as the default value.


Solution

  • We can use the function's __default__ property to "freeze" defaults (and __kwargs__ to "freeze" default keyword arguments):

    def freeze_defaults(func):
         defaults = func.__defaults__
         kwdefaults = func.__kwdefaults__
         @functools.wraps(func) 
         def wrapper(*args, **kwargs): 
             func.__defaults__ = copy.deepcopy(defaults) 
             func.__kwdefaults__ = copy.deepcopy(kwdefaults)
             return func(*args, **kwargs) 
         return wrapper
    

    Now:

    @freeze_defaults
    def test(x=[]):
        x.append(5)
        return x
    
    print(test())
    print(test())
    

    Prints:

    [5]

    [5]

    And even:

    @freeze_defaults
    def test(x=['hello!']):
        x.append(5)
        return x
    
    print(test())
    print(test())
    

    Prints:

    ['hello!', 5]

    ['hello!', 5]