views:

127

answers:

4

I have a couple of lists of items:

specials = ['apple', 'banana', 'cherry', ...]
smoothies = ['banana-apple', 'mocha mango', ...]

I want to make a new list, special_smoothies, consisting of elements in smoothies that start with the elements in specials. However, if specials is blank, special_smoothies should be identical to smoothies.

What's the most Pythonic way to do this? Is there a way to do this without a separate conditional check on whether specials is blank?

+2  A: 

There are a couple of ways to do it without an explicit check on specials. But don't do them.

if specials:
  special_smoothies = [x for x in smoothies if any(True for y in specials if x.startswith(y))]
else:
  special_smoothies = smoothies[:]
Ignacio Vazquez-Abrams
+1 for "But don't do them." Python isn't about doing tricky hacks.
Skilldrick
+1, but any(x.startswith(y) for y in specials) would be enough.
e-satis
Thanks for the response. One question: what's the reason for doing `special_smoothies = smoothies[:]` instead of just `smoothies`?
Kevin Stargel
@Kevin, using `smoothies[:]` makes `special_smoothies` a copy instead of a reference. If you use a reference and at some point change either `special_smoothies` or the original `smoothies` you would change both lists.
tgray
@Kevin, FYI Alex Martelli's `list(smoothies)` accomplishes the same thing as `smoothies[:]`
tgray
-1 `any(True for ...)` is not idiomatic; it's about as poxy as `if foo == True:`
John Machin
+4  A: 

Since you want the behavior for empty specials to be different from the natural limit of the behavior for non-empty, you do need to special-case:

if specials:
    specialsmoothies = [x for x in smoothies
                        if any(x.startswith(y) for y in specials)]
else:
    specialsmoothies = list(smoothies)

In other words, you want the behavior for empty specials to be "all smoothies are specials", while the natural limit behavior would be to say that in that case "no smoothie is special" since none of them starts with one of the prefixes in specials (since there are no such prefixes in that case). So, one way or another (an if/else or otherwise) you do need to make a special case in your code to match the special, irregular case you want in its semantics.

Alex Martelli
Didn't you misplaced a parenthesis after startswith ?
e-satis
@e-satis, no, the parentheses are just fine -- I just edited the code to break up the listcomp over 2 lines so you don't have to scroll to edit. Why do you thing the parens are at all problematic?
Alex Martelli
A: 

Why complicate things? I think this is the most readable. Alex and Ignacio both give good reasons for not avoiding the else clause.

special_smoothies = []
if specials:
    for smoothy in smoothies:
        for special in specials:
            if smoothy.startswith(special):
                special_smoothies.append(smoothy)
else:
    special_smoothies = smoothies[:]
Skilldrick
This certainly IS the most readable. Anyone sees an objection to such an approach compared to [x for x in smoothies if any(True for y in specials if x.startswith(y))]? I guess it would take much longer on big lists, no?
Morlock
@Morlock: As far as I can tell, all the approaches are nested loops, just with different syntax. I guess the one problem with this approach is that if one special was a sub-string of another (e.g. 'bana' and 'banana') then a matching smoothy would get added twice.
Skilldrick
I strongly disagree about these 8 lines being _at all_ more readable than the 4-lines suggestions in all other answers, especially with the weird `[:]` idiom that's also in the accepted answer vs the readable `list()` in the other two. Plus, @skilldrick's correctly pointing out that this allegedly "readable" code has a potential bug that all other solutions, based on `any`, effortlessly avoid (and the bug's related to the fact that this code's performance is inferior since it doesn't terminate the inner loop when done, but always runs to the end). Builtins like `any` are **good**!!!
Alex Martelli
@Alex The 'weird `[:]` idiom' is from the Python tutorial: http://docs.python.org/tutorial/introduction.html#listsI guess how readable the rest of it depends on your proficiency in Python. List comprehensions are great, but to the uninitiated they can look a bit like Perl one-liners... Otherwise, I agree with you.
Skilldrick
@Skilldrick, I think my proficiency in Python is reasonably good, yet I have an easier time reading words `list` and `any` than I do reading `[:]` (how do you pronounce it -- bracketcolonbracket?-). Long listcomps can be daunting to beginners, yes,, but the `any` built-in is really a must (pronounceable, readable, perfect semantics, fast) and replacing it with an imperfect loop is not "readable" -- it's just _wrong_!-)
Alex Martelli
@Alex Don't worry, I don't doubt your proficiency in Python - I've learnt a lot from your videos! I stand corrected about `any` - I wasn't aware of that before now.
Skilldrick
+1  A: 

str.startswith() accepts tuple as an argument:

if specials:
    specialsmoothies = [x for x in smoothies if x.startswith(tuple(specials))]
else:
    specialsmoothies = list(smoothies)
J.F. Sebastian