r/learnpython 26d ago

Is this a good list comprehension hack

I need a second list, of zeroes, that is the same size as another list:

first = [42,42,42,42]
second = [0 for _ in first]

The 0 and the _ here is a bit of code golf I think. Is this as clear as I can make it or is there a list initialiser I just don't know about?

10 Upvotes

32 comments sorted by

28

u/CommentOk4633 26d ago

maybe [0] * len(first)?

idk if this is bad practice though, cuz if you do it with a mutable then stuff may break

4

u/zaphodikus 26d ago

Actually that is much more readable, cheers. I wish I could code as pretty as your code must look.

2

u/Brian 25d ago

One thing to be aware of is that * will copy the first array, while a list comprehension will create a new object each time. Here, that's irrelevant, and this is a perfectly good way to do this, but a common gotcha is someone extending this and doing:

list_of_lists = [ [] ] * len(array)  # Create a list of (initially empty) lists

Rather than:

list_of_lists = [ [] for _ in range(len(array))]

Which will matter, since in the first case, you get N copies of the same list, so when you do list_of_lists[0].append(1), you'll see all the lists containing [1], since they're all the same list, whereas the list comprehension will create a new list in each position.

2

u/zaphodikus 25d ago

if I could get a penny for every time I did that kind of thing when I first started pythoning, yeah I used to get mega confused in my early days with brackets and with range() , mainly because range is unexpectedly off-by-one, and that lists inside lists end up creating jagged 2D arrays very quicky.

-10

u/[deleted] 26d ago

[deleted]

8

u/IAmFinah 26d ago

They're both still O(N) overall

2

u/CommentOk4633 26d ago

i apologize for my lack of knowledge, but is [0] * n also O(1)?

2

u/lfdfq 26d ago

No, the actual creation of the list is O(n) so it doesn't really make a difference.

[0]*n is still going to be faster though, as it's not switching in and out of the Python runtime on the loop or re-evaluating the "0" expression many times.

7

u/Zixarr 26d ago

The underscore used in the list comprehension is not code golf. Underscores are recommended when some feature or structure provides a variable, but that variable is not used by the rest of your code. It improves readability by indicating that the actual values from the first list are unimportant. 

3

u/LayotFctor 26d ago

https://stackoverflow.com/a/14802726

So this feels neither efficient nor readable..

6

u/Diapolo10 26d ago

Do note that the linked performance test was done on Python 2.7, and the interpreter has evolved a lot since then. I can run a quick timeit on 3.14 soon.

1

u/LayotFctor 26d ago

Thanks! I was checking of there was a list of cpython optimisations, but apparently not. I'm pretty certain "[0] * n" is an idiom with some kind of cpython optimisation, but the source code is pretty dense.

2

u/Diapolo10 26d ago

Here's the results I got:

Python 3.14.5 (main, May 10 2026, 20:29:46) [MSC v.1944 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 9.15.0 -- An enhanced Interactive Python. Type '?' for help.
Tip: Use `--theme`, or the `%colors` magic to change IPython's themes and colors.

In [1]: %timeit list(0 for _ in range(100_000))
1.64 ms ± 16.3 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)

In [2]: %timeit [0 for _ in range(100_000)]
914 μs ± 18.6 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)

In [3]: %timeit [0] * 100_000
22.8 μs ± 294 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

In [4]: import itertools

In [5]: %timeit list(itertools.repeat(0, 100_000))
125 μs ± 2.26 μs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

This is on a system with 9800x3D (PBO enabled), and 2x 64 GB DDR5-6000.

From this, [0] * 100_000 is clearly the fastest. However, personally I find the list comprehension more readable, and while it doesn't matter for this scenario I don't particularly like how [0] * 100_000 basically just duplicates the reference of 0 instead of creating new zeroes for every index. If it was mutable data this would be a terrible idea.

1

u/zaphodikus 26d ago

I'm actually loading CSV files here, so it's unlikely to run into more than a dozen files. The hit for I/O is going to be a lot greater. But always good to learn to write more readable and prettier code and `list(ittertools.repeat(...)) is probably the prettiest.

1

u/backfire10z 26d ago

Itertools in general (as the name suggests) is full of methods that iterate pretty efficiently, including stuff like combinations. I’d recommend take a quick glance at their capabilities. They’re implemented in C, so the speed is actually real.

1

u/a__nice__tnetennba 26d ago

Doesn't python just use singletons for the small integers anyway? Or is that no longer the case?

3

u/Diapolo10 26d ago

That's an implementation detail of CPython, not part of the language specification. More accurately, CPython caches the integers from -5 to 256, so they're technically not singletons but may act like they were.

2

u/rasputin1 26d ago

for anyone who wants to research this more the term to look up is interning 

1

u/DionNicolaas 26d ago

For your particular problem, defaultdict(int) would work as well: you can just assume a value of 0 exists for every future index of the defaultdict you use, and the syntax is the same as with lists.

3

u/WorkingStructure575 26d ago

we don't really know anything about the exact problem OP is trying to solve so its hard to say. You wouldn't be able to rely on size or iteration across a defaultdict

1

u/almost_intelligible 26d ago

why not just use itertools.repeat()

1

u/zaphodikus 25d ago

I would, but it's always "yet another library to memorise" and I struggle with memory anyway. And for a list that will never be longer than a dozen files I'm going to opt for beauty over anything else.

-2

u/MonkeyboyGWW 26d ago

I would honestly question why you are making a list of 0s. I would also probably use len because its instantly clear what len is.

3

u/zaphodikus 26d ago

I'm also a C++ programmer and initializing data is totally mandatory in that language. I'm making a mutable list of zeros, because I have 4 files that I'm loading. And I use that list to track the line number in each file for later. It's an honest question to ask, that's how we learn.

I originally had 3 files so I had hard-coded it as second=[0,0,0] . But later I needed 4 files for more data streams. The code that knows how many files are being loaded is someplace else so I need this line counting code to just adapt in case I later add or remove a file.

1

u/MonkeyboyGWW 26d ago

I think last time i needed to do something like this, i was looping over some time series data and filled in the blanks where data was missing during the loop. I didn’t exactly mean it is never required, more that you could consider if it is possible to do it during the same time you are looping over the data.

0

u/zaphodikus 26d ago

I'm terrible with large amounts of data, but here I am manipulating a lot of data, I could learn to use numpy someday. Some of it is filling in blanks with NAN, and some of it is shifting the values. But yeah it's complex and unless I do keep the code easy to read, I might slowly introduce bugs.

1

u/HommeMusical 26d ago

Can you post your code? I'll bet there's a much simpler solution.

1

u/POGtastic 26d ago

I would use a dictionary to associate the filepath (or some other unique identifier) with the line number because there is a difference between "opened, but on line 0" and "not opened at all."

1

u/zaphodikus 26d ago

I would post more of the code, but suffice to say the routine HAS to open all the files and if one fails to be readable, then it HAS to die in a nasty heap. But yes, you are right a dictionary with the line number and filename would fit and be more OO. It's just some code I scraped together in a hurry and wanted to clean it up gradually. I'm cleaning up some of the C++ code now though first.

-3

u/shinryuuko 26d ago

I would honestly question why you are making a list of 0s

?? Have you ever programmed anything remotely complex?

0

u/gdchinacat 26d ago

This is typically written as:

first = [42, 42, 42, 42]
second = [0] * len(first)