Programming & Tech Tips for Everyone

Python List Changes After Assignment: How to Copy a List

If you copy a Python list like this:

new_list = old_list

Any modifications made to the new_list also change the old_list. This may be confusing.

It happens because new_list is actually not a copy of old_list. Instead, it is a reference to the same object in memory.

Python assignment illustrated
“Copying” a list just creates an alias for the same object in memory.

To create a completely independent copy of a list, use the copy module’s deepcopy() function.

import copy

new_list = copy.deepcopy(old_list)

All in all, you can use any of these approaches to create a copy of a list in Python:

  1. copy() method. Creates a shallow copy.
  2. [:] slicing operator. Creates a shallow copy.
  3. list() function. Creates a shallow copy.
  4. copy.copy() function. Creates a shallow copy.
  5. copy.deepcopy() function. Creates a deep copy.

In this guide you are going to learn:

  • Why assignment does not copy.
  • What are references.
  • Shallow copying.
  • Deep copying.
  • Mutability.
  • 5 ways to copy a list in Python.

Assignment (=) in Python

If you use the assignment operator (=) to copy a list in Python, you are not actually copying.

Instead, you name a new variable that refers to the original list. This new variable thus acts as an alias to the original list.

Let’s see an example where we:

  • Create a list.
  • Assign or “copy” the list to a new variable.
  • Change the first number in the original list.
  • Print both lists.
numbers = [1, 2, 3]
new_numbers = numbers

# Only change the original list
numbers[0] = 100

print(numbers)
print(new_numbers)

Output:

[100, 2, 3]
[100, 2, 3]

As you can see, we only changed the first element in the original numbers list. However, this change also took place in the new_numbers list even though apparently we did not touch it.

This happens because numbers and new_numbers are actually the very same list object.

Assignment in Python
Under the hood, both lists point to the same blob in memory.

Another way to verify this is by checking the memory address of these objects.

In Python, you can use the id() method to find out the memory address of any object.

Let’s check the memory addresses of both numbers and new_numbers.

print(id(numbers))
print(id(new_numbers))

Output:

140113161420416
140113161420416

The IDs are the same.

This verifies that the numbers and new_numbers are both aliases pointing to the same list object in memory.

To put it all together, think of the list object as a chunk of memory without a name. The numbers and new_numbers are only names via which you can access the list object.

So when you create a new variable and assign a list object to it, you are introducing a new reference label to the original object.

To recap, the assignment operator (=) creates a new reference to an object in memory. It does not copy anything. This applies to lists as well as any other object in Python.

Next, let’s take a look at how you can actually copy list objects in Python.

The Copy Module in Python

As you learned, you cannot use the assignment operator to copy objects in Python. This is why there is a separate module, copy dedicated to copying Python objects.

The two key functions in the copy module are:

  • copy.copy()
  • copy.deepcopy()

Let’s take a look at what these functions do and what are the differences.

Shallow Copy: copy.copy()

In Python, a shallow copy can be created using copy.copy() function.

A shallow copy solves our problem of copying a list in a way it does not depend on the original list.

For example:

import copy

numbers = [1, 2, 3]

# Independent copy of 'numbers' list
new_numbers = copy.copy(numbers)

numbers[0] = 100

print(numbers)
print(new_numbers)

Output:

[100, 2, 3]
[1, 2, 3]

As you can see, changing the first element in the original list did not change the copied list.

Let’s also verify the objects are not the same by using the id() function:

print(id(numbers))
print(id(new_numbers))

Output:

139764897739904
139764897692480

Horray! Now you know how to create a copy of a list in Python.

However, it is important to notice that sometimes you might have a list that consists of lists.

In this case, the shallow copy (copy.copy() function) does not behave the way you expect. Instead, it creates an independent copy of the outer list, but the inner lists are bound to the original list.

Let me show what this means by running a simple experiment by:

  • Creating a list of lists.
  • Creating a shallow copy of the list.
  • Modifying the first list’s first object.
import copy

numbers = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
new_numbers = copy.copy(numbers)

numbers[0][0] = 1000

print(numbers)
print(new_numbers)

Output:

[[1000, 2, 3], [4, 5, 6], [7, 8, 9]]
[[1000, 2, 3], [4, 5, 6], [7, 8, 9]]

As you can see, changing the first element of the first list affects the copied version of the list.

But why does this happen? We even used the copy.copy() so the new_numbers should be a copy of the original list.

Let’s compare the IDs of the lists to see whether they are the same object or not:

print(id(numbers))
print(id(new_numbers))

Output:

140602923260928
140602923261632

Even the IDs do not match! This means new_numbers should truly be a copy of numbers.

And indeed it is.

But why do the values still change in the copied list?

This is because copy.copy() creates a shallow copy.

In this case, it means the whole list is copied, but the lists inside the list are not. In other words, the inner lists are tied to the lists in the original list object.

I know this sounds strange, but this is how it works.

Let’s verify this by checking the IDs of the lists inside the list:

print(id(numbers[0]), id(numbers[1]), id(numbers[2]))
print(id(new_numbers[0]), id(new_numbers[1]), id(new_numbers[2]))

Output:

140685291558208 140685291498496 140685291708160
140685291558208 140685291498496 140685291708160

As you can see, all the inner lists IDs are the same.

So the outer list is copied but the inner lists are still bound to the original list of lists.

To put it together, here is an illustration of how copy.copy() works on a list of lists.

Shallow copy in Python

But how do you create a completely independent copy of this kind of list?

To create a completely independent copy, use the copy.deepcopy() function.

Deep Copy: copy.deepcopy()

Another key function in the copy module is the deepcopy() function.

This function creates a completely independent copy of a list or any other compound object in Python.

For example, let’s repeat the example in the previous chapter using deepcopy():

import copy

numbers = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
new_numbers = copy.deepcopy(numbers)

numbers[0][0] = 1000

print(numbers)
print(new_numbers)

Output:

[[1000, 2, 3], [4, 5, 6], [7, 8, 9]]
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

As you can see, changing the first element in the first list did not affect the copied list.

In other words, you have successfully created a completely independent copy of the original list.

Deep copy in python
No part in the deep-copied list points to the original list. Thus, a deep copy creates a truly independent copy.

Awesome. Now you understand how copying lists works in Python.

I recommend you to play with the examples to truly learn what is happening.

This guide would not be complete if we did not talk about copying other objects than lists. It is important to realize everything related to copying lists applies to copying any other Python object.

However, the behavior of copying can be different depending on the data type.

Copying a Number Object in Python

Let’s repeat the very first example in this guide using integers instead of lists.

In other words, let’s:

  • Create a number variable.
  • Copy the number to another variable using the assignment operator.
  • Change the original number.
  • See what happens to the copy.
a = 10
b = a

a = 50

print(a, b)

Output:

50 10

As you can see, changing the original number a did not change the number b. Before reading this article, this is probably something you would expect.

But this contradicts what we said earlier about copying Python objects: A Python object cannot be copied using the assignment operator.

However, looking at the above example, it seems b is an independent copy of a because changing a does not change b.

Even though this happens, b is not a copy of a. This is important to understand.

You can verify this by checking the IDs of the variables before changing the value in a.

a = 10
b = a

print(id(a))
print(id(b))

Output:

9789280
9789280

As you can see, the IDs match. In other words, a and b are both aliases to the same integer object in memory.

But why does changing a not change b then?

It all boils down to mutability.

In Python, integer objects are immutable. Immutability means you cannot change an integer object.

On the other hand, a list is a mutable object. This means you can change the list object directly.

  • If you have an integer object, you cannot directly change it. Instead, you have to create a new integer object with a different value.
  • If you have a list object, you can change its elements directly, without creating a new list object.

This is mutability in a nutshell.

Now, let’s go back to the example of copying an integer. Let’s print the IDs of the variables before and after changing the value in a:

a = 10
b = a

print(f"Before assignment id(a) = {id(a)}, id(b) = {id(b)}")

a = 50

print(f"After assignment id(a) = {id(a)}, id(b) = {id(b)}")

Output:

Before assignment id(a) = 9789280, id(b) = 9789280
After assignment id(a) = 9790560, id(b) = 9789280

The IDs of variables a and b match before assigning a new value to a but not afterward.

In other words, before changing the value in a:

  • a and b point to the same integer object in memory.

And after changing the value in a:

  • a points to a new integer object in memory but b still points to where a used to point.

So after assigning a new value to variable a, it points to a new integer object in memory. This happens because an integer is an immutable data type. The integer object 10 cannot directly be changed to some other value. Instead, a completely new integer object needs to be created.

Here is a quick illustration of how the code works:

Python variable copying
Assigning a new integer to a creates a new integer object where the variable a points to.

To recap, the assignment operator (=) cannot be used to copy objects in Python. However, when dealing with immutable objects, it looks as if this was the case. But it is not.

If someone tells you to copy a variable, technically you need to use copy.copy() or copy.deepcopy() instead of the assignment operator.

  • However, when dealing with immutable objects, this is unnecessary, as the behavior is the same regardless of whether you used copy module or assignment operator.
  • But with mutable objects, you need to use the copy module to create a real copy of the object.

At this point, you understand why the assignment operator does not copy objects in Python. You also learned how to use the copy module to create copies of Python objects.

Now that you understand what is a shallow copy and a deep copy, let’s put it all together by taking a look at 5 common ways to copy a list in Python.

5 Ways to Copy a List in Python

There are five main ways to copy a list in Python:

  1. copy() method.
  2. [:] slicing operator.
  3. list() function.
  4. copy.copy() function.
  5. copy.deepcopy() function.

Let’s see examples of each of these

1. The copy() Method

As of Python 3.3, a list comes with a built-in copy() method. This method creates a shallow copy of the list.

For example:

numbers = [1, 2, 3]
new_numbers = numbers.copy()

print(numbers)
print(new_numbers)

Output:

[1, 2, 3]
[1, 2, 3]

2. The [:] Slicing operator

In Python, slicing means pulling a range of values from an iterable, such as a list.

Slicing goes with the syntax of:

iterable[start:end]

Where start specifies the starting index and end specifies the ending index.

If you do not specify the start parameter, slicing starts from the very first element. If you do not specify the end, the slicing ends at the very last element.

Calling iterable[:] returns a slice that represents the whole iterable. In other words, it returns a copy of a list when called on a list.

Notice that this also creates a shallow copy.

For instance:

numbers = [1, 2, 3]
new_numbers = numbers[:]

print(numbers)
print(new_numbers)

Output:

[1, 2, 3]
[1, 2, 3]

3. The list() Function

To convert an object to a list in Python, you can use the built-in list() function. This function creates a new list object for the input argument.

When you call the list() function on a list in Python, you force it to create a copy of the original list. The type of this copy is also shallow.

For instance:

numbers = [1, 2, 3]
new_numbers = list(numbers)

print(numbers)
print(new_numbers)

Output:

[1, 2, 3]
[1, 2, 3]

4. The copy.copy() Function

As discussed earlier in this guide, there is a dedicated module copy for copying Python objects.

One of the functions in this module is the copy() function. This function creates a shallow copy of a Python object. You can use copy.copy() to create a copy of a list.

For instance:

import copy

numbers = [1, 2, 3]
new_numbers = copy.copy(numbers)

print(numbers)
print(new_numbers)

Output:

[1, 2, 3]
[1, 2, 3]

5. The copy.deepcopy() Function

The only way to create a truly independent deep copy of a Python object is by using the copy.deepcopy() function.

The difference between a shallow copy and a deep copy is only relevant to objects that consist of objects. This is comprehensively explained earlier in this guide.

You can use copy.deepcopy() to create a deep copy of a list.

For example:

import copy

numbers = [1, 2, 3]
new_numbers = copy.deepcopy(numbers)

print(numbers)
print(new_numbers)

Output:

[1, 2, 3]
[1, 2, 3]

Conclusion

Today you learned how to copy a Python list successfully.

To recap, copying using the assignment operator is not possible. Instead of copying, it creates a new alias to the original object. This means changing the original object changes the “copy” as well.

To truly copy an object in Python, use the copy module’s functions:

  • copy.copy() for a shallow copy where compound objects are bound to the original object.
  • copy.deepcopy() for a deep and a completely independent copy.

Further Reading

Python Interview Questions

Best Websites to Learn Python

Share

Share on twitter
Share on linkedin
Share on facebook
Share on pinterest
Share on email