Open In App

Least Astonishment and the Mutable Default Argument in Python

Improve
Improve
Like Article
Like
Save
Share
Report

The principle of Least Astonishment (also known as least surprise) is a design guideline that states that a system should behave the way it is intended to behave. It should not change its behavior in an unexpected manner to astonish the user. By following this principle, designers can help to create user interfaces that are more user-friendly and less frustrating to use. This can be especially important in the context of complex systems, where unexpected behavior can lead to confusion and errors.

Now let’s see how this principle is violated by Python while using mutable types as arguments in Python.

Default arguments in Python

Default arguments are those arguments that are pre-defined in the function head, in case these arguments are not passed during function calling the default arguments are implicitly accepted by the function. Default arguments are defined in the following manner:

Python3




# b and c are default arguments
def function_with_default_arguments(a, b=1, c=2):
    print(f"a = {a} "
          f"b = {b} "
          f"c = {c}")
 
 
if __name__ == '__main__':
    function_with_default_arguments(0# only a is passed


Output:

a = 0 b = 1 c = 2

Mutable Default Arguments in Python

When default arguments are among the mutable data types like list, dictionary, set, etc. the code often shows unusual behavior. Here, argument a is of integer type, whereas b is the mutable list. Each time the function gets called, a gets reinitialized but b being mutable gets retained. Being a default argument b should be reinitialized too, but this behavior of value retention violates the principle of least astonishment.

Python3




# b is mutable default argument which will cause an issue
def function_with_default_arguments(a, b=[]): 
    b.append(1)
    print(f"a = {a},"
          f"b = {b}")
 
if __name__ == '__main__':
     # function called once
    function_with_default_arguments(0
    # function called twice
    function_with_default_arguments(1
    # function called thrice
    function_with_default_arguments(2


Output:

a = 0, b = [1]
a = 1, b = [1, 1]
a = 2, b = [1, 1, 1]

Fixing the issue

Whenever a mutable type is provided as the default argument, assign it to be None value in the function head. Hence, the principle of least astonishment is preserved by applying the above-mentioned fix. It is generally considered best practice to avoid using mutable data types as default arguments in Python functions. Assign the type to that argument within the function body as follows:

Python3




# b is assigned as None
def function_with_default_arguments(a, b=None): 
    if b is None:
      # b is assigned as list here
        b = [] 
    b.append(1)
    print(f"a = {a}, "
          f"b = {b}")
 
 
if __name__ == '__main__':
    # function called once
    function_with_default_arguments(0)
    # function called twice
    function_with_default_arguments(1)
    # function called thrice
    function_with_default_arguments(2)


Output:

a = 0, b = [1]
a = 1, b = [1]
a = 2, b = [1]

Time complexity: O(1).

Auxiliary space: O(1).



Last Updated : 01 Feb, 2023
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads