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 obj开发者_JS百科ect from the list that matches the parameter I'm looking for. Is this method unreasonable? The dangling [0] makes me feel a bit insecure.
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)
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)
This post has a custom find()
function which works quite well, and a commenter there also linked to this method based on generators. Basically, it sounds like there's no single great way to do this — but these solutions aren't bad.
Personally I'd to this in a proper loop.
actor = None
for actor in self.actors:
if actor.name == actorName:
break
It's quite a bit longer, but it does have the advantage that it stops looping as soon as a match is found.
精彩评论