Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python Function arguments and mutability #184

Open
Qingquan-Li opened this issue May 12, 2022 · 0 comments
Open

Python Function arguments and mutability #184

Qingquan-Li opened this issue May 12, 2022 · 0 comments
Labels

Comments

@Qingquan-Li
Copy link
Owner

Qingquan-Li commented May 12, 2022

1. Function arguments

Arguments to functions are passed by object reference, a concept known in Python as pass-by-assignment.
When a function is called, new local variables are created in the function's local namespace by binding the names in the parameter list to the passed arguments.

def birthday(age):
    '''Celebrate birthday!'''
    age = age + 1

timmy_age = 7

birthday(timmy_age)
print('Timmy is', timmy_age)  # Output: Timmy is 7

# 1. timmy_age and age reference the same object.
# 2. Assigning the parameter age with a new value doesn't change timmy_age.
# 3. Since timmy_age has not changed, "Timmy is 7" is displayed.

1.1 Keyword Arguments and Positional Arguments

  • Python provides for keyword arguments that allow arguments to map to parameters by name, instead of implicitly by position in the argument list.
    Good practice is to use keyword arguments for any function containing more than approximately 4 arguments.
  • Keyword arguments can be mixed with positional arguments, provided that the keyword arguments come last.
  • A function can have a default parameter value for one or more parameters, meaning that a function call can optionally omit an argument, and the default parameter value will be substituted for the corresponding omitted argument.
def student_info(stu_id, name, pronouns, age, school="Hogwarts"):
    print(f'{name}, {stu_id}, {pronouns}, Age: {age}, {school}')

student_info(123, name='Bob', pronouns='he/him/his', age=18)
# Bob, 123, he/him/his, Age: 18, Hogwarts

2. Mutability of the argument object

The semantics of passing object references as arguments is important because modifying an argument that is referenced elsewhere in the program may cause side effects outside of the function scope.
When a function modifies a parameter, whether or not that modification is seen outside the scope of the function depends on the mutability of the argument object.

  • If the object is immutable, such as a string or integer, then the modification is limited to inside the function.
    Any modification to an immutable object results in the creation of a new object in the function's local scope, thus leaving the original argument object unchanged.
  • If the object is mutable, then in-place modification of the object can be seen outside the scope of the function.
    Any operation like adding elements to a container or sorting a list that is performed within a function will also affect any other variables in the program that reference the same object.

2.1 How to pass a mutable object to a function

The following program illustrates how the modification of a list argument's elements inside a function persists outside of the function call.

def modify(num_list):
    num_list[1] = 99

my_list = [10, 20, 30]
modify(my_list)
print(my_list)  # my_list still contains 99!

Sometimes a programmer needs to pass a mutable object to a function but wants to make sure that the function does not modify the object at all.
One method to avoid unwanted changes is to pass a copy of the object as the argument instead, like in the statement my_func(num_list[:]):

def modify(num_list):
    num_list[1] = 99  # Modifying only the copy

my_list = [10, 20, 30]
modify(my_list[:])  # Pass a copy of the list 

print(my_list)  # my_list does not contain 99!

2.2 How to provide a mutable object as a default parameter

A common error is to provide a mutable object, like a list, as a default parameter.
Such a definition can be problematic because the default argument object is created only once, at the time the function is defined (when the script is loaded), and not every time the function is called.
Modification of the default parameter object will persist across function calls, which is likely not what a programmer intended.
The below program demonstrates the problem with mutable default objects and illustrates a solution that creates a new empty list each time the function is called:

""" Default object modification
This program shows a function append_to_list() that has an empty list as default value of my_list.
A programmer might expect that each time the function is called without specifying my_list, a new
empty list will be created and the result of the function will be [value].
However, the default object persists across function calls. """

def append_to_list(value, my_list=[]):
    my_list.append(value)
    return my_list

numbers = append_to_list(50)  # default list appended with 50
print(numbers)  # [50]
numbers = append_to_list(100)  # default list appended with 100
print(numbers)  # [50, 100]
""" Solution: Make a new list
The solution replaces the default list with None, checking for that value, and then creating a new
empty list in the local scope if necessary. """

# Use default parameter value of None
def append_to_list(value, my_list=None):
    if my_list == None:
    	# Create a new list if a list was not provided
        my_list = []

    my_list.append(value)
    return my_list

numbers = append_to_list(50)  # default list appended with 50
print(numbers)  # [50]
numbers = append_to_list(100)  # default list appended with 100
print(numbers)  # [100]

Reference: zyBooks > Principles in Information Technology and Computation > 12.8 Function arguments & 12.9 Keyword arguments and default parameter values

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant