Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Alternative to list comprehension if there will be only one result

I'm starting to get used to list comprehension in Python but I'm afraid I'm using it somewhat improperly. I've run into a scenario a few times where I'm using list comprehension but immediately taking the first (and only) item from the list that is generated. Here is an example:

actor = [actor for actor in self.actors if actor.name==actorName][0] 

(self.actors contains a list of objects and I'm trying to get to the one with a specific (string) name, which is in actorName.)

I'm trying to pull out the object from the list that matches the parameter I'm looking for. Is this method unreasonable? The dangling [0] makes me feel a bit insecure.

like image 534
timfreilly Avatar asked Aug 10 '11 06:08

timfreilly


People also ask

What is faster than list comprehension?

For loops are faster than list comprehensions to run functions.

Are list comprehensions necessary?

List comprehensions are useful and can help you write elegant code that's easy to read and debug, but they're not the right choice for all circumstances. They might make your code run more slowly or use more memory.

Why might you use a list comprehension instead of a loop?

List comprehensions are often not only more readable but also faster than using “for loops.” They can simplify your code, but if you put too much logic inside, they will instead become harder to read and understand.

What is the difference between list comprehension and generator?

The only difference between Generator Comprehension and List Comprehension is that the former uses parentheses.


2 Answers

If you want to take the first match of potentially many, next(...) is great. But if you expect exactly one, consider writing it defensively:

[actor] = [actor for actor in self.actors if actor.name==actorName] 

This always scans to the end, but unlike [0], the destructuring assignment into [actor] throws a ValueError if there are 0 or more than one match. Perhaps even more important then catching bugs, this communicates your assumption to the reader.

If you want a default for 0 matches, but still catch >1 matches:

[actor] = [actor for actor in self.actors if actor.name==actorName] or [default] 

P.S. it's also possible to use a generator expression on right side:

[actor] = (actor for actor in self.actors if actor.name==actorName) 

which may be a tiny bit more efficient (?). You could also use tuple syntax on the left side — looks more symmetric but the comma is ugly and too easy to miss IMHO:

(actor,) = (actor for actor in self.actors if actor.name==actorName) actor, = (actor for actor in self.actors if actor.name==actorName) 

(anyway list vs tuple syntax on left side is purely cosmetic doesn't affect behavior)

like image 38
Beni Cherniavsky-Paskin Avatar answered Oct 06 '22 01:10

Beni Cherniavsky-Paskin


You could use a generator expression and next instead. This would be more efficient as well, since an intermediate list is not created and iteration can stop once a match has been found:

actor = next(actor for actor in self.actors if actor.name==actorName) 

And as senderle points out, another advantage to this approach is that you can specify a default if no match is found:

actor = next((actor for actor in self.actors if actor.name==actorName), None) 
like image 139
zeekay Avatar answered Oct 06 '22 00:10

zeekay