views:

181

answers:

2

The original list project_keys = sorted(projects.keys()) is [101, 102, 103, 104, 105, 106, 107, 108, 109, 110] where the following projects were deemed invalid this year: 108, 109, 110.

Thus:

for project in projects.itervalues():
# The projects dictionary is mapped to the Project class
    if project.invalid:
    # Where invalid is a Bool parameter in the Project class
     project_keys.remove(project.proj_id)  

print project_keys

This will return a list of integers (which are project id's) as such:

[101, 102, 103, 104, 105, 106, 107]

Sweet.

Now, I wanted it try the same thing using a list comprehension.

project_keys = [project_keys.remove(project.proj_id) for project in projects.itervalues() if project.invalid  

print project_keys

This returns:

[None, None, None]

So I'm populating a list with the same number as the removed elements but they're Nones?

Can someone point out what I'm doing wrong?

Additionally, why would I use a list comprehension over the for-if block at the top? Conciseness? Looks nicer?

+6  A: 

Your list comprehension works using side-effects. Just executing it should update project_keys to give the result you want.

[project_keys.remove(project.proj_id)
 for project in projects.itervalues()
 if project.invalid]

The return value from remove is None. Assigning the result of the list comprehension to project_keys is where you are going wrong.

A simple loop is probably clearer here though. A list comprehension that uses side-effects can be confusing.

However you can solve your problem in a slightly different way:

project_keys = sorted(project.proj_id
                      for project in projects.itervalues()
                      if not project.invalid)

This keeps the projects you are interested in, instead of removing those that you're not interested in. The example I gave above uses a generator expression instead of a list comprehension, but it would work with either.

Mark Byers
It appears you beat Dirk by 12 seconds, but his is an actual list comprehension, while yours is (as I write this, maybe you'll edit it later) strictly speaking a generator expression and not a list comprehension.
John Y
@John Y: True. :) I'll think about the best way to fix it. Thanks.
Mark Byers
Nice edit. Evidently Dirk decided your fuller explanation makes his superfluous, and my own was both less informative and too snarky, so mine will soon be gone as well.
John Y
@Mark Byers: That's neat! Thanks muchly :)
Az
Marks' second solution--to make a copy of the list--is much better than to use `remove()` while you iterate over the list. It is risky to modify a collection inside a loop that iterates over its members. It may work as expected or fail catastrophically, depending on the implementation of the list and iterator classes. Even if in-place modification works on `list` s, what happens if someone feeds your code a different kind of collection? Or Python's `list` implementation changes in the next version? Whereas The copy strategy always works, even on immutable collections, like tuples
Dan Menes
@Dan He iterates over `projects.itervalues()` while removing from `project_keys`, so no, he's not modifying what he's iterating on.
badp
@bp: You are correct. Don't know how I missed that.
Dan Menes
+3  A: 

You, sir, have misunderstood list comprehensions.

What you probably wanted (in words)

I want to remove all project ids that are invalid.

What you wrote

project_keys = [project_keys.remove(project.proj_id)
                for project in projects.itervalues() if project.invalid]

What is actually going on

dummy = []
for project in projects.itervalues():
  if project.invalid:
    dummy.append(project_keys.remove(project.proj_id)) #what are you
project_keys = dummy                                   #removing items from?
del dummy                                            

What is actually going on (now with more "functional")

mapped-fun = lambda project: project_keys.remove(project.proj_id)
filtering-fun = lambda project: project.invalid
project_keys = map(mapped-fun, filter(filtering-fun, projects.itervalues()))

As you can see, list comprehensions are not syntactical sugar around for loops. Rather, list comprehensions are syntactical sugar around map() and filter(): apply a function to all items in a sequence that match a condition and get a list of results in return.

Here, by function it is actually meant a side-effect-free transformation of input into output. This means that you "cannot" use methods that change the input itself, like list.sort(); you'll have to use their functional equivalents, like sorted().

By "cannot", however, I don't mean you'll get error messages or nasal demons; I mean you are abusing the language. In your case, the evaluation of the list comprehension that happens as you assign it to a variable does indeed produce the intended side-effects -- but does it produce them on the intended variables?

See, the only reason why this can execute without an error is that before this list comprehension, there was another list called project_keys and it's that list you are actually changing!

Lists comprehensions are a result of functional programming, which rejects side effects. Keep that in mind when using lists comprehensions.


So here's a thought process you can use to actually get the list comprehension you wanted.

What you actually wanted (in words)

I want all project ids that are valid (= not invalid.)

What you actually wanted

dummy = []
for project in projects.itervalues():
  if not project.invalid:
    dummy.append(project.proj_id)
project_keys = dummy
del dummy

What you actually wanted (now with more functional)

mapped-fun = lambda project: project.proj_id
filtering-fun = lambda project: not project.invalid
project_keys = map(mapped-fun, filter(filtering-fun, projects.itervalues()))

What you actually wanted (now as a list comprehension)

project_keys = [project.proj_id for project in projects.itervalues()
                if not project.invalid]
badp
Dude, that's pretty damn awesome :)! Mark Bayers' one worked so I checked that as a viable answer (and it was pretty good and it was there earlier). Can't thank you enough for taking the time to spell out the whole thing.
Az
It's okay. Related: http://meta.stackoverflow.com/questions/9731/fastest-gun-in-the-west-problem
badp