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).