views:

528

answers:

6

List comprehensions can be useful in certain situations, but they can also be rather horrible to read.. As a slightly exaggerated example, how would you indent the following?

allUuids = [x.id for x in self.db.query(schema.allPostsUuid).execute(timeout = 20) if x.type == "post" and x.deleted is not False]
+2  A: 

How about:

allUuids = [x.id for x in self.db.query(schema.allPostsUuid).execute(timeout = 20) 
                   if (x.type == "post" and x.deleted is not False)]

Generally, long lines can be avoided by pre-computing subexpressions into variables, which might add a minuscule performance cost:

query_ids = self.db.query(schema.allPostsUuid).execute(timeout = 20)
allUuids = [x.id for x in query_ids
                   if (x.type == "post" and x.deleted is not False)]

By the way, isn't 'is not False' kind-of superfluous ? Are you worried about differentiating between None and False ? Because otherwise, it suffices to leave the condition as only: if (x.type == "post" and x.deleted)

Eli Bendersky
I second that. Generally lines of more than 80 characters should be avoided in all languages.
Piotr Lesnicki
Agreed, but Python's syntax rules often encourage/force longer lines. Its whitespace handling in general is about my only complaint about the language.
Peter Rowell
With carefully chosen variables, and spliting lines with parenthesis or brackets, i never had to resort to too long lines (the problem is more often to avoid self.long_foo.very_long_bar.baz(....) using temporaries)
Piotr Lesnicki
About the "By the way", it is not totally superfluous, take the case x.deleted = None, for example.
Ali A
+4  A: 

For me that's too much. Maybe it's just a terrible example, since "type" and "deleted" would clearly be part of the db query.

I tend to think that if a list comprehension spans multiple lines it probably shouldn't be a list comprehension. Having said that, I usually just split the thing at "if" like other people have and will answer here.

Ali A
+10  A: 

It depends on how long they are. I tend to structure them like so:

[x.id for x
 in self.db.query(schema.allPostsUuid).execute(timeout=20)
 if x.type == 'post' 
    and x.deleted is not False
    and ...
    and ...]

That way every expression has its own line.

If any line becomes too big I like to extract it out in a lambda or expression:

transform = lambda x: x.id
results = self.db.query(schema.allPostsUuid).execute(timeout=20)
condition = lambda x: x.deleted is not False and ... and ...
[transform(x) for x in results if condition(x)]

And then if a lambda becomes too long it gets promoted to a function.

orestis
these are NOT equivalent - the list comprehension is many times faster, as it doesn't have to do any function lookups. the appropriate translation would use for loops.
Claudiu
Except for the performance hit , this is a very readable example !
Geo
How would for loops prevent the function lookup? Also note that the loop in list comprehensions is implemented in C, and is therefore faster than the plain one.
orestis
Just a note -- you can use multiple if statements instead of using "and" multiple times. Kind of a funny little syntax truth.
cdleary
cdleary: Thanks! Never knew that. Seems a bit dirty, though :)
orestis
+8  A: 

Where I work, our coding guidelines would have us do something like this:

all_posts_uuid_query = self.db.query(schema.allPostsUuid)
all_posts_uuid_list = all_posts_uuid_query.execute(timeout=20)
all_uuid_list = [
    x.id 
    for x in all_posts_uuid_list 
    if (
        x.type == "post" 
        and 
        not x.deleted  # <-- if you don't care about NULLs / None
    )
]
That's quite pretty.
Ali A
That's how I do it
orip
+1  A: 
allUuids = [x.id 
            for x in self.db.query(schema.allPostsUuid).execute(timeout = 20) 
            if x.type == "post" and x.deleted is not False]
J.F. Sebastian
+2  A: 

You should not use a list comprehension for that.

List comprehensions are an awesome feature, but they are meant to be shortcuts, not regular code.

For such a long snippet, you should use ordinary blocs :

allUuids = []
for x in self.db.query(schema.allPostsUuid).execute(timeout = 20) :
    if x.type == "post" and x.deleted is not False :
        allUuids.append(x.id)

Exactly the same behavior, much more readable. Guido would be proud of you :-)

e-satis
this is a teeny bit slower, as you have to build the array step by step
Claudiu
I think the list comprehension does exactly the same under the hood. It's not because it's a one line expression that this is a one time operation...
e-satis
e-statis: the function is the same, but list comprehensions can be significantly faster
orip
Hum, I don't have time to benchmark it so I'll have to take your word :-) But anyway, readability matters more that speed in Python...
e-satis
In the Python culture, you usually choose readability over speed. Python isn't a fast language anyway...
e-satis