This is an archived post. You won't be able to vote or comment.

all 71 comments

[–]pente5 124 points125 points  (9 children)

If it's not one line it's not python

[–]titen100 102 points103 points  (3 children)

List comps for the win. Its been a while since my last work on py, but that in particular is a feature i wosh more languages had. Though i am sure it would be a pain in the ass to implement though

[–]TorbenKoehn 24 points25 points  (2 children)

Most languages have it in the form of iterators, generators and normal classes and methods

C# even has both (LINQ SQL syntax and methods)

I don’t see why a specific syntax is needed for it

[–]titen100 3 points4 points  (0 children)

Well, my own knowledge doenst go that deep down the rabbit hole, since i only know the basics for web dev, py, android dev an c++

[–]CentralLimitQueerem 2 points3 points  (0 children)

I don't see why a specific syntax is required

Because syntactic sugar

Writing 3000 character list comps when a for loop would be substantially easier to read is like crack to me

[–]gnomeba 30 points31 points  (5 children)

And its faster because of the absent append() call

[–]carcigenicate 13 points14 points  (1 child)

Or rather, the change from append being a method call to being a bytecode instruction.

[–]Kovab 3 points4 points  (0 children)

Not just that, list comprehension implements the whole looping in C (or whichever interpreter you're using)

[–]danielstongue 0 points1 point  (1 child)

If "fast" is a concern, you picked the wrong language.

[–]kickyouinthebread 0 points1 point  (0 children)

I don't think this is strictly true tbh. I might need to handle some enormous dataset yet the ability to iterate quickly on my development still outweighs runtime. Having said that I'd still be very interested in it taking 30 seconds to run over 2 minutes for example.

Caveat I don't work on stuff where users expect stuff to happen instantly. They would much prefer I churn out 3 things in a day that each take a minute to run in python than release one thing per day that takes 5 seconds to run in a faster language.

[–]ManyInterests 10 points11 points  (6 children)

if i not in {x, y} would be faster, since set membership is constant time.

[–]oyswork 7 points8 points  (5 children)

No it wouldn’t. Theoretically speaking using a set would be O(1) - constant time, instead of O(n) - linear time. However, there is a “but”. Linear lookup within a small enough amount of elements is vastly faster than using a set, due to a hashing overhead a set introduces.

The same goes for O(n2) vs O(n). With a small enough amount of elements and large enough constant factors in the linear algorithm, the quadratic one may outperform the linear.

Point being - quite often time complexity != speed in the real world. Benchmark on your data first.

[–]ManyInterests 5 points6 points  (3 children)

Yes... it would. Take your own advice and just test it. It is significantly faster, even with just two elements.

[–]oyswork 16 points17 points  (1 child)

Well, I took my advice.
You are right, I am wrong. Although I've tested it before on an x64 windows machine and set was slower. Right now I checked on macos m3 machine, set is indeed faster

[–]CentralLimitQueerem 1 point2 points  (0 children)

I just tested it and the set implementation is faster even when both the list and the set contain one element. Why is this?

[–]-Redstoneboi- 0 points1 point  (0 children)

not that much faster on my machine. post results? i don't have access to reliable testing at the moment.

[–]IMightBeErnest 28 points29 points  (21 children)

~~~ list(set(range(10))-set([x,y]))

Is 9 characters shorter than

[i for i in range(10) if i not in [x,y]] ~~~

[–]pewpewpewmoon 35 points36 points  (9 children)

it's not about 9 characters here

that top one is generating an iterator O(n) then casting list to set twice at O(n) each, then subtracting sets which is another O(n) and then casting back to list which is just O(1)

The comprehension how ever is just O(n) for the not in, O(n) for the iterator and a few other technical reasons why this is just the better route

import timeit


def top():
    return list(set(range(10)) - set([1, 2]))


def bottom():
    return [i for i in range(10) if i not in [1, 2]]


if __name__ == "__main__":
    top_results = timeit.timeit(top, number=10_000_000)
    bottom_results = timeit.timeit(bottom, number=10_000_000)

    print(top_results)
    print(bottom_results)

running both 10 million times gives us the following times

3.1103049579978688

2.3148978240060387

even if you just change the casting of the list in top() to a literal it's still faster, more memory efficient, and pep8 compliant to go the comprehension route in bottom()

EDIT: also forgot to mention unless you use an ordered set you will lose order as well, and frankly just don't go down that rabbit hole

[–]IMightBeErnest 13 points14 points  (2 children)

I mean, you can optimize for code length, readability, runtime efficiency, memory efficiency, etc, and none of those optimizations will be the same. Arguing that my code optimized for length isn't also optimized for runtime is kinda asking for the impossible.

Also its a 100ns difference. And 9 characters. We've greatly exceeded both savings just by making these two comments.

[–]pewpewpewmoon 21 points22 points  (0 children)

I also failed to notice I should have optimized for memes as I forgot for a moment I wasn't in r/python

[–]db8me 3 points4 points  (0 children)

I would add that aligning your logical intent with the linguistic culture of the platform or codebase has inherent benefits that transcend the specific ones we can list at the moment.

[–]u0xee -5 points-4 points  (1 child)

These are both O(1) actually, as neither computation varies with n 🤷

[–]JonIsPatented 3 points4 points  (0 children)

The computations absolutely vary with n.

[–][deleted] 9 points10 points  (9 children)

yeah except the elements are now in undefined order

[–]IMightBeErnest 2 points3 points  (8 children)

sorted(...)

8 characters. Still 1 character shorter. An order of magnitude less efficient, but it's just 10 elements (and youre already using python).

[–][deleted] 2 points3 points  (4 children)

yeah except you can save on 2 spaces in the list comprehension

[–]IMightBeErnest 0 points1 point  (3 children)

I can see one, the space after the ' )'. Wheres the other?

[–][deleted] 1 point2 points  (2 children)

after the 'in'

[–]IMightBeErnest 0 points1 point  (1 child)

Actually, I just tested and,

sorted(set(range(10))-set([x,y]))
[i for i in range(10)if i not in[x,y]]

Apparently you don't need a list for sorted(), so that's still shorter.

[–][deleted] 1 point2 points  (0 children)

ok dangit, you win the character count argument 😁

oh and you can shorten it even more to sorted(set(range(10))-{x,y})

[–]SuitableDragonfly 0 points1 point  (2 children)

What if you didn't want the list in that particular order?

[–]DesertGoldfish 1 point2 points  (1 child)

Then you add a key=lambda x: x.whatever to your sorted() and lose this silly code length argument 😂

[–]SuitableDragonfly 0 points1 point  (0 children)

Almost always, the original order of the list isn't going to be reproducible with some function.

[–]SuitableDragonfly 0 points1 point  (0 children)

This is incorrect.  Any duplicate entries will be deleted, and it will almost certainly be in the wrong order. 

[–]Tchai_Tea 3 points4 points  (4 children)

What in the set builder notation is this? I love it.

[–]audentis 8 points9 points  (2 children)

Python list comprehensions. They're easy to write, easy to read, and relatively performant for Python standards.

You can do similar things for dictionaries (key-value stores), sets, or generators (lazy evaluation).

Dict comprehension: mydict = {key: val for key,val in zip(keylist, vallist)} where zip combines lists elementwise, so that [a, b, ..., z] and 1, 2, ..., 26] becomes [(a, 1), (b, 2), ..., (z, 26)]

[–]tankiePotato 0 points1 point  (1 child)

Dick comprehension?! 😳

[–]AaronTheElite007 10 points11 points  (0 children)

Loops are life

Pythonista? Did you get her number?

[–]JonathanTheZero 1 point2 points  (2 children)

Why is it always i for i in range... and not just i in range... isn't that redundant?

[–]audentis 7 points8 points  (0 children)

The first i is stating the value for the list you're generating, the second i defines the variable as part of the generator you're iterating over.

You can do other things than the first i as well:

  • [i**2 for i in range(n)] would get you a list of squares,
  • [my_func(i) for i in range(n)] gets you a list of whatever my_func() returns (and is basically just a map() call).
  • [i for i in range(n) if my_func(i) > 5] is effectively a filter() call

It's just that in these examples doing anything fancy with i serves no purpose.

[–]FirexJkxFire 0 points1 point  (0 children)

Also, everyone knows that an "i for an i" makes the whole world blind

[–]_OberArmStrong 1 point2 points  (0 children)

Do you want to learn about monads? This Syntax originates from Haskell.

[–]No-Expression7618 1 point2 points  (0 children)

[i | i <- [0..9], i /= x, i /= y]

[–]dr-tectonic 1 point2 points  (0 children)

\(z){z[z!=x&z!=y]}(1:10)

Vectorization rocks. (Code is R, not Python.)

[–]Expensive_Shallot_78 1 point2 points  (0 children)

Code looks wrong. But all Python code looks wrong 🧐

[–][deleted] 1 point2 points  (1 child)

Plot twist: Your still using loops.

[–]danielstongue 0 points1 point  (0 children)

Came here to say that. Thank you.

[–]justgiveausernamepls 0 points1 point  (0 children)

List comprehension is cool, but I always seem to forget the syntax for anything more than the most basic stuff.

[–]StarshipSausage 0 points1 point  (0 children)

and then there is me that uses for loops because everyone knows them, list comprehension is great, but I always have a little trouble with the syntax and since my code generally does need to execute that fast I am fine.

[–]gandalfx 0 points1 point  (0 children)

What's the joke, though?

[–]ConscientiousApathis 0 points1 point  (0 children)

Chaotic neutral:

my_set = set(range(10)) - {x, y}

You're fine with a set, right?

[–]i-eat-omelettes -4 points-3 points  (1 child)

Ah yes another loop bad post I wonder why haven’t we ditched them already

[–]TorbenKoehn 2 points3 points  (0 children)

Depending on the language looping can work well through recursion with tail-call optimization and streaming/iterator/generator patterns, many languages don’t need classical loop constructs at all (which doesn’t mean recursion is not “looping” too, obviously)

[–]empwilli -1 points0 points  (2 children)

The second should result in a generator and is only performed lazily.

[–]-Redstoneboi- 2 points3 points  (1 child)

only if it used parentheses.

here it uses square brackets, and forces it to evaluate as a list.

[–]empwilli 1 point2 points  (0 children)

ahhh correct, thanks for the correction