Hello again! Welcome to the sixth installment of the How to Python series. Today, we’re going to learn how to clone or copy a list in Python. Unlike most articles in this series, there are actually quite a few options—some better than others.
In short, there are so many different ways to copy a list. In this article alone, we share eight solutions. If you’re looking for something safe, use the copy method (i.e.
my_list.copy()). Otherwise, feel free to try slicing (i.e.
my_list[:]) or the list constructor (i.e.
Table of Contents
If you’re not interested in digging through this article, I’ve shared all the material in a YouTube video. In addition to live coding most of the solutions to the list copying problem, I’ve also share some performance metrics as well as my solution to the challenge below. If for nothing else, I’d love it if you ran over to YouTube and boosted my metrics a little bit (like, comment, subscribe, etc.).
Imagine that we have a list:
my_list = [27, 13, -11, 60, 39, 15]
And, we want to create a duplicate of this list, so we can modify their contents independently:
my_list = [27, 13, -11, 60, 39, 15] my_duplicate_list = [27, 13, -11, 60, 39, 15] my_list.append(17) print(my_list) # prints [27, 13, -11, 60, 39, 15, 17] print(my_duplicate_list) # prints [27, 13, -11, 60, 39, 15]
How would we go about doing that? Well, before we dive in, there are a couple of topics we should probably cover first. After all, cloning can be a bit counterintuitive, so it’s important that we take a step back to discuss duplicate references and deep copies.
If you’ve come to this article, it’s probably because you’ve tried cloning a list by hand, and you’ve run into some problems. For instance:
my_list = [27, 13, -11, 60, 39, 15] my_duplicate_list = my_list # done
Unfortunately, this doesn’t really do the job. After all, we haven’t actually duplicated the list. We’ve simply stored the reference to it in another variable. If we try to modify our duplicate list, we’ll modify the original as well. Take a look:
my_duplicate_list.append(7) print(my_duplicate_list) # prints [27, 13, -11, 60, 39, 15, 7] print(my_list) # prints [27, 13, -11, 60, 39, 15, 7]
So, clearly that’s not what we want. Instead of duplicating our list, we’ve created an alias—another variable that refers to the same list.
In addition, we should probably cover something known as deep copying. Let’s say we have a list that contains lists:
my_list = [, , [-11], , , ]
If we decide to perform a simple copy on this list, we’ll end up with some strange behavior:
my_list_copy = copy(my_list) # a placeholder copy function print(my_list_copy) # prints [, , [-11], , , ] # prints as expected
Alright, so no problems yet. In fact, we can even append information to the new list without any problems:
my_list_copy.append() print(my_list_copy) # prints [, , [-11], , , , ] print(my_list) # prints [, , [-11], , , ]
However, if we decide to modify any of the nested lists, we’ll run into problems:
my_list_copy.append(12) print(my_list_copy) # prints [[27, 12], , [-11], , , , ] print(my_list) # prints [[27, 12], , [-11], , , ]
That’s because our copy operation only duplicated the outer list. In other words, we created two separate lists, but each list stores the same exact references. Modifying a reference in one list modifies it in the other list.
A deep copy method would make sure to copy both the outer list and the inner list. Keep that in mind as we move forward.
If we want to clone a list, we have several options. Let’s take a look.
Copy a List by Brute Force
As always, Python offers several quick solutions to this problem. However, before we get to those, I want to actually examine cloning from a beginner’s perspective. In other words, let’s skip the API for now and try to implement our own cloning function:
def clone(my_list): my_list_clone = list() for item in my_list: my_list_clone.append(item) return my_list_clone
That seems simple enough. Basically, we just iterate over the list and copy each item into the new list. In fact, we can even make this solution more pythonic:
def clone(my_list): return [item for item in my_list]
How’s that for a one-liner? The problem is this method doesn’t perform a deep clone. Unfortunately, implementing deep clone by hand is a bit out of scope for this tutorial, but I challenge you to try it yourself. As a hint, you’ll basically want to build a recursive copy function.
Copy a List Using a Slice
If you thought the comprehension was slick, wait until you see this slice:
my_list = [27, 13, -11, 60, 39, 15] my_duplicate_list = my_list[:] # done
If you’re unfamiliar with slices, basically this takes a “subset” of the list from end-to-end. Normally, we would use slices like this:
my_list[:4] # [27, 13, -11, 60] my_list[3:] # [60, 39, 15]
Without indices, the slice will duplicate the entire list. Again, however, this will not perform a deep copy.
Copy a List Using the List Constructor
In the world of software design patterns, there’s a creation pattern known as the copy constructor. Instead of taking a set of input parameters for construction, a copy constructor takes a reference to an initialized object and produces a copy of it. Luckily for us, Python provides a copy constructor for lists:
my_list = [27, 13, -11, 60, 39, 15] my_duplicate_list = list(my_list)
Unfortunately, even this method does not provide a deep copy, but it is much more readable than the slice.
Copy a List Using a Starred Expression
Recently, dev.to user, Leah Einhorn, tipped me off on yet another way to copy a list in Python:
my_list = [27, 13, -11, 60, 39, 15] my_duplicate_list = [*my_list]
For a lack of a better term, I’ll go ahead and call this solution a “starred expression” because that’s the syntax error I got when I messed it up:
SyntaxError: can't use starred expression here
That said, I think the technical term for this solution would be iterable unpacking which I’ve talked about in the following articles:
At any rate, this works by expanding the list into comma separated arguments. In other words, instead of storing the list inside another list, we actually unpack the list into individual items and load them directly into a new list.
As is the case with most solutions in this list, iterable unpacking also falls prey to the same shallow copy issues. As a result, you wouldn’t be able to use this solution to copy a list of lists (see the challenge below).
Copy a List Using the Copy Function
As it turns out, Python 3.3+ includes a copy function for lists directly. To use it, call
copy() on the list and store the results:
my_list = [1, -5, 13, 4, 7] my_duplicate_list = my_list.copy()
In my opinion, this is the most readable option, and I believe it’s also the most efficient version. In fact, the performance should be similar to the slice. However, Python changes a lot, so the other methods mentioned here may be just as performant—check out my benchmarks below for more info.
Copy a List Using the Copy Package
Python wouldn’t be Python without its endless package collection. As a result, you can probably imagine there’s some API we can leverage to perform the copying for us. After all, why should we be reinventing the wheel? Here’s how it works:
import copy my_list = [27, 13, -11, 60, 39, 15] my_duplicate_list = copy.copy(my_list)
Due to the generic nature of this method, we take a bit of a hit in performance. That said, it’s quite clear what we’re trying to accomplish here. Unfortunately, we still fail to produce a deep copy. Thankfully, the copy package has a solution for that:
my_list = [, , [-11], , , ] my_duplicate_list = copy.deepcopy(my_list)
At long last, we have achieved a true deep copy of our nested lists. Of course, deep copy is entirely overkill if the list is only one layer deep. See the challenge below if you’re interested in other ways of accomplishing a deep copy.
Copy a List Using Multiplication
Honestly, I hesitated putting this one in here because it’s simply ridiculous, but it’s a fun abuse of the multiplication operator:
my_list = [27, 13, -11, 60, 39, 15] my_list_copy = my_list * 1
Again, this does not perform a deep copy, but that’s hardly the point. We just used the multiplication operator to duplicate a list. Normally, we would use the multiplication operator to populate a list:
my_list =  * 100 # a list with 100 zeroes
Instead, we’ve decided to abuse it for the purposes of generating a list copy. If you think this is funny, take a look at this list of strange language features on Stack Overflow. After writing this section, I stumbled upon that article while trying to find other ways to abuse Python language features.
If you haven’t watched the video summary yet, now would be a great time to check out the performance section. After all, I’m snagging all the metrics from there.
At any rate, to check performance, I like to use the
timeit library which allows us to check the speed of a code snippet. And if we run all our code snippets, we’ll get a nice relative comparison. To start, we have to build up our set of strings:
setup = """ pens = ["Sidney Crosby", "Evgeni Malkin", "Kris Letang"] import copy """ brute = """ my_list_clone = list() for item in pens: my_list_clone.append(item) """ comprehension = """ my_duplicate_list = [item for item in pens] """ sliced = """ my_duplicate_list = pens[:] """ constructor = """ my_duplicate_list = list(pens) """ starred = """ my_duplicate_list = [*pens] """ copied = """ my_duplicate_list = pens.copy() """ copy_pack = """ my_duplicate_list = copy.copy(pens) """ multiplication = """ my_duplicate_list = pens * 1 """
With these strings in place, it’s just a matter of running them using the
>>> import timeit >>> min(timeit.repeat(setup=setup, stmt=brute, repeat=10)) 0.36501209999994444 >>> min(timeit.repeat(setup=setup, stmt=comprehension, repeat=10)) 0.24934929999994893 >>> min(timeit.repeat(setup=setup, stmt=sliced, repeat=10)) 0.07904620000022078 >>> min(timeit.repeat(setup=setup, stmt=constructor, repeat=10)) 0.15885279999997692 >>> min(timeit.repeat(setup=setup, stmt=starred, repeat=10)) 0.056014600000025894 >>> min(timeit.repeat(setup=setup, stmt=copied, repeat=10)) 0.081436100000019 >>> min(timeit.repeat(setup=setup, stmt=copy_pack, repeat=10)) 0.37341589999982716 >>> min(timeit.repeat(setup=setup, stmt=multiplication, repeat=10)) 0.07483669999987796
And, there we have it! All eight solutions fully tested. Naturally, I was intrigued by the fastest solutions, so I decided to see how they scaled. Here’s the updated setup string which generates a list of 1000 items:
setup = """ pens = ["Sidney Crosby" for _ in range(1000)] import copy """
Here are the updated test results with the top 4 solutions:
>>> min(timeit.repeat(setup=setup, stmt=sliced, repeat=10)) 3.097306200000048 >>> min(timeit.repeat(setup=setup, stmt=starred, repeat=10)) 2.9019645000000764 >>> min(timeit.repeat(setup=setup, stmt=copied, repeat=10)) 3.033651100000043 >>> min(timeit.repeat(setup=setup, stmt=multiplication, repeat=10)) 2.897438200000124
Overall, it looks like all four solutions scale at about the same rate. In other words, there’s not much of a difference beyond initial overhead. Perhaps they get much worse with more items, but I don’t really have the patience to test any further. Perhaps someone can take a look for us!
At any rate, if you’re interested in fast solutions, check out the slice, starred expression, copying, and multiplication solutions. Of course, I’d say the built-in copy function is the way to go—regardless of speed.
Now that we’ve covered several copying mechanisms in Python, I thought would be fun to propose a bit of a challenge. In particular, write some code to duplicate a nested list. For example:
pens_forwards = [ ["Dominik Simon", "Sidney Crosby", "Jake Guentzel"], ["Alex Galchenyuk", "Evgeni Malkin", "Patric Hornqvist"], ["Zach Aston-Reese", "Nick Bjugstad", "Bryan Rust"], ["Brandon Tanev", "Teddy Blueger", "Dominik Kahun"] ] pens_forwards_copy = duplicate(pens_forwards) # implement this
Then, you should be able to confirm you copy worked by testing the sublists for identity:
pens_forwards is pens_forwards_copy # Should return false pens_forwards is pens_forwards_copy # Should return false
When you’re ready, share your solution in the comments. It should work for any type of nested list, but we’ll assume a depth of 1 (i.e. a 2-dimensional list). In addition, we’ll assume items in the deepest list are primitives or at least immutable (i.e. numbers, strings, etc.). Finally, you cannot use the
deepcopy function. Instead, you should implement your own.
Here’s my solution!
If you’d like to try your hand at this challenge, share your solution on Twitter with the hashtag #RenegadePython. If I see it, I’ll give it a share!
A Little Recap
With this installment of How to Python, we’re finally starting to get into some more interesting language features and topics. As a result, we’re finding a lot of ways to solve the same problem – some good, some bad. At any rate, here are all the ways we can clone a list in Python:
my_list = [27, 13, -11, 60, 39, 15] # Clone a list by brute force my_duplicate_list = [item for item in my_list] # Clone a list with a slice my_duplicate_list = my_list[:] # Clone a list with the list constructor my_duplicate_list = list(my_list) # Clone a list with a starred expression my_duplicate_list = [*my_list] # Clone a list with the copy function (Python 3.3+) my_duplicate_list = my_list.copy() # preferred method # Clone a list with the copy package import copy my_duplicate_list = copy.copy(my_list) my_deep_duplicate_list = copy.deepcopy(my_list) # Clone a list with multiplication? my_duplicate_list = my_list * 1 # do not do this
All of these methods will get the job done, but only one of these methods will actually perform a deep copy if needed. At any rate, we’re done here.
If you found this article helpful, consider sharing it on social media or leaving a comment below. Until next time!
As we roll into 2023, I wanted to take a moment to celebrate my most recent milestone in academia. I'm a PhD candidate!
Foo, bar, and baz: what do they mean and where do they come from? Let's find out together.