[NCLUG] help w/ python list comprehension

Sean Reifschneider jafo at tummy.com
Tue Aug 27 20:29:54 MDT 2013


On 08/21/2013 09:54 AM, Mike Cullerton wrote:
>     for item in items:
>         missing = False
>         for tag in tags:
>             # do we have a tag (ignore tag == '')
>             # can we find it in item.tags
>             if tag and not tag.lower() in [tag.lower() for tag in item.tags]:
>                 missing = True
>                 break
>         if not missing: items_by_tag.append(item)
>     return items_by_tag

One thing I'd recommend against is the duplicate use of tag in the
comprehension:

            if tag and not tag.lower() in [tag.lower() for tag in item.tags]:

Instead, I tend to use "x" for things that are temporary in a
comprehension:

            if tag and not tag.lower() in [x.lower() for x in item.tags]:

That reduces any confusion about the multiple uses of tag.

You're also computing the item.tags multiple times, so it might be more
obvious to make it:

    for item in items:
        item_tags_lower = [x.lower() for x in item.tags]
        missing = False
        for tag in tags:
            # do we have a tag (ignore tag == '')
            # can we find it in item.tags
            if tag and not tag.lower() in item_tags_lower:
                missing = True
                break
        if not missing: items_by_tag.append(item)
    return items_by_tag

One place you can use a list comprehension here is to get rid of the "if
tag":

    for item in items:
        item_tags_lower = [x.lower() for x in item.tags]
        missing = False
        for tag in [x for x in tags if x]:
            if not tag.lower() in item_tags_lower:
                missing = True
                break
        if not missing: items_by_tag.append(item)
    return items_by_tag

But you're also re-computing the tag.lower() as well, so:

    tags_lower = [x.lower() for x in tags if x]:
    for item in items:
        item_tags_lower = [x.lower() for x in item.tags]
        missing = False
        for tag in tags_lower:
            if not tag in item_tags_lower:
                missing = True
                break
        if not missing: items_by_tag.append(item)
    return items_by_tag

But, I believe what you're trying to do here is to return a list of items
that have a tag in the list of tags, right?  This is something that sets
can help us with, because we can use logic operators on sets:

    >>> set([1,2,3]) & set([4,5,6])
    set([])
    >>> set([1,2,3]) & set([3,4,5])
    set([3])

So:

    tags_set = set([x.lower() for x in tags if x])
    return [x for x in items if
            set([x.lower() for x in item.tags]) & tags_set]

That has the benefit of not turning an iterator into a static list, if you
are dealing with a huge list of items, say from a database...

Is that, as Stephen says, less clear?  Well, I'd say that the last example
above is much more clear than the original code.  But it could probably be
made more clear with:

    tags_set = set([x.lower() for x in tags if x])

    def any_matching_tags(search):
        return set([x.lower() for x in item.tags]) & tags_set

    return [x for x in items if any_matching_tags(x)]

Sean


More information about the NCLUG mailing list