Kodeclik Logo

Our Programs

Courses

Learn More

Schedule

Kodeclik Blog

How to Set or Modify a Variable after It’s defined in Python

To answer the question of if or whether and how to modify a Python variable, we need to understand some basic concepts of working with variables in Python. We need to understand the distinction between the value of a variable and the address of a variable (which is the location where it is stored).

For this purpose, let us review the id() function in Python. Think of id() as the way to find the address or location of a variable. Checkout our blogpost on Python pointers for more detail but here are some basic self-contained examples.

Basic usage of the id() function

The id() function in Python is a built-in function that returns a unique integer identifier for an object.

x = 42
print(id(x))  # Output: 140712833452016 (this number may vary)

Comparing objects using id()

You can use id() to check if two variables are being stored in the same location (and thus refer to the same object):

a = [1, 2, 3]
b = a
c = [1, 2, 3]

print(id(a) == id(b))  # Output: True
print(id(a) == id(c))  # Output: False

In this example, a and b refer to the same list object, while c is a separate list with the same contents. This is why id() is very useful.

Checking lists using id()

Let us apply the id() function on lists. We will initialize two different list variables to the same list but will learn that each list gets its own unique identifier:

list1 = [1, 2, 3]
list2 = [1, 2, 3]
print(id(list1) == id(list2))  # Output: False

Checking id()s of Objects in Functions

The id() function can be useful for understanding object references in functions:

def modify_list(lst):
    print("Inside function:", id(lst))
    lst.append(4)

my_list = [1, 2, 3]
print("Before function:", id(my_list))
modify_list(my_list)
print("After function:", id(my_list))

The output of the above code will be something like:

Before function: 140248989328000
Inside function: 140248989328000
After function: 140248989328000

This example demonstrates that the list object's id remains the same even when modified inside a function.

Ok, now that we know how to look at variables and how they are stored, we are ready to see how Python sets variables to values and how we modify variables after they are defined.

To begin, we observe that Python’s approach to variable modification involves two distinct mechanisms: in-place modification and reassignment.

Python set variables after they are defined

In-place modification for mutable objects

This refers to changing the contents of a mutable object without altering its identity or memory address. Some objects in Python such as lists, dictionaries, and sets can be modified in place. Thus they are mutable objects.

# List example
my_list = [1, 2, 3]
print(id(my_list))  # Output: 140712834927872
my_list.append(4)
print(my_list)  # Output: [1, 2, 3, 4]
print(id(my_list))  # Output: 140712834927872 (same as before)

# Dictionary example
my_dict = {'a': 1, 'b': 2}
my_dict['c'] = 3
print(my_dict)  # Output: {'a': 1, 'b': 2, 'c': 3}

# Set example
my_set = {1, 2, 3}
my_set.add(4)
print(my_set)  # Output: {1, 2, 3, 4}

Ressignment for immutable objects

Reassignment involves creating a new object and updating the variable to reference this new object. This is the only option for immutable types like integers, strings, and tuples.

# Integer reassignment
x = 5
print(id(x))  # Output: 140712833451984
x += 1
print(x)  # Output: 6
print(id(x))  # Output: 140712833452016 (different from before)

# String reassignment
s = "hello"
print(id(s))  # Output: 140712834663792
s += " world"
print(s)  # Output: "hello world"
print(id(s))  # Output: 140712834664944 (different from before)

# Tuple reassignment
t = (1, 2, 3)
print(id(t))  # Output: 140712834928512
t = t + (4,)
print(t)  # Output: (1, 2, 3, 4)
print(id(t))  # Output: 140712834928672 (different from before)

When you "modify" an immutable object, you're actually creating a new object and reassigning the variable to it.

The distinction between mutable and immutable is very important. To recap, for mutable objects, in-place modification affects all variables referencing that object. For immutable objects, reassignment creates a new object, leaving other variables referencing the original object unchanged.

Multiple References

Understanding the difference between these two mechanisms is crucial for predicting how changes to variables will affect other parts of your code, especially when dealing with multiple references to the same object.

# Mutable object (list)
a = [1, 2, 3]
b = a
a.append(4)
print(a)  # Output: [1, 2, 3, 4]
print(b)  # Output: [1, 2, 3, 4]
print(id(a) == id(b))  # Output: True

# Immutable object (integer)
x = 5
y = x
x += 1
print(x)  # Output: 6
print(y)  # Output: 5
print(id(x) == id(y))  # Output: False

In the mutable example, both a and b reference the same list object, so modifying it through either variable affects the underlying object. In the immutable example, x is reassigned to a new integer object, while y still references the original value.

Next, we need to study how mutability interacts with scope. Consider the following examples.

Local variables with mutable and immutable objects

Consider the below piece of code:

def modify_data(immutable, mutable):
    immutable += 1
    mutable.append(4)
    print(f"Inside function: immutable = {immutable}, mutable = {mutable}")

x = 5  # immutable
y = [1, 2, 3]  # mutable
modify_data(x, y)
print(f"Outside function: x = {x}, y = {y}")

The output will be:

Inside function: immutable = 6, mutable = [1, 2, 3, 4]
Outside function: x = 5, y = [1, 2, 3, 4]

In this example, x (an integer) is immutable, while y (a list) is mutable. When passed to the function, the local variable immutable gets a new value, but this doesn't affect the original x. However, the local variable mutable refers to the same list object as y, so modifications to it are reflected outside the function. This demonstrates how mutability affects the behavior of local variables.

Global variables with mutable and immutable objects

Now let us consider the below code:

count = 0  # immutable
items = []  # mutable

def update_data():
    global count
    count += 1
    items.append(count)

update_data()
update_data()
print(f"Count: {count}, Items: {items}")

Here, count is an immutable global variable, while items is a mutable global variable. The global keyword is needed to modify count because it's immutable, and assignment would otherwise create a new local variable. For items, no global keyword is needed to modify it because it's mutable, and the function can directly access and modify the global list object. In either case both variables are modified.

The output will be:

Count: 2, Items: [1, 2]

Nonlocal variables with mutable and immutable objects

Here is likely the most complex piece of code to illustrate the interplay between scoping and mutability:

def outer():
    x = 5  # immutable
    y = [1, 2, 3]  # mutable
    
    def inner():
        nonlocal x
        x += 1
        y.append(4)
        print(f"Inside inner: x = {x}, y = {y}")
    
    inner()
    print(f"Inside outer: x = {x}, y = {y}")

outer()

The output is:

Inside inner: x = 6, y = [1, 2, 3, 4]
Inside outer: x = 6, y = [1, 2, 3, 4]

This example shows how nonlocal variables behave with mutable and immutable objects. The nonlocal keyword is used for the immutable variable x to allow modification of the variable in the outer scope. For the mutable list y, no nonlocal declaration is needed because the inner function can directly modify the list object referenced by the outer scope's variable. This demonstrates that mutability affects how we interact with nonlocal variables in nested functions.

In all these cases, the key distinction is that immutable objects (like integers) require explicit declarations (global or nonlocal) to modify the original variable, while mutable objects (like lists) can be modified directly without such declarations, as long as the variable itself is accessible in the current scope.

Summary

To recap what we have learnt here, the id() function in Python is a built-in function that returns a unique integer identifier for an object, essentially representing its memory address. This function is useful for understanding object references and comparing objects. When applied to variables, lists, and objects within functions, id() helps demonstrate how Python handles object storage and referencing. The function reveals that variables pointing to the same object share the same id, while separate objects with identical contents have different ids.

Second, Python employs two mechanisms for variable modification: in-place modification and reassignment. In-place modification applies to mutable objects like lists, dictionaries, and sets, allowing changes to the object's contents without altering its identity or memory address. Reassignment, on the other hand, creates a new object and updates the variable to reference this new object, which is the only option for immutable types like integers, strings, and tuples. Understanding the distinction between these mechanisms is crucial for predicting how changes to variables will affect other parts of the code, especially when dealing with multiple references to the same object.

Finally, make sure to understand the interplay between scoping and mutability and ensure that you have the access necessary to modify the variable under consideration.

Want to learn Python with us? Sign up for 1:1 or small group classes.

Kodeclik sidebar newsletter

Join our mailing list

Subscribe to get updates about our classes, camps, coupons, and more.

About

Kodeclik is an online coding academy for kids and teens to learn real world programming. Kids are introduced to coding in a fun and exciting way and are challeged to higher levels with engaging, high quality content.

Copyright @ Kodeclik 2024. All rights reserved.