How to rewrite this as list comprehension?

Alex Martelli aleaxit at yahoo.com
Tue Dec 19 07:59:17 EST 2000


"Serge Beaumont" <serge at sergebeaumont.com> wrote in message
news:91nj2q$s3p$1 at buty.wanadoo.nl...
> Hello,
>
> I'm trying to get my head around comprehensions. How would I rewrite this
> method?
>
>   def deadThings(self, type = None):
>     deadThings = self.destroyed[:]
>     for thing in self.things:
>       if thing.destroyed:
>         if type:
>           if isinstance(thing, type):
>             deadThings.append(thing)
>         else:
>           deadThings.append(thing)
>     return deadThings

Writing this as a _single_ list comprehension will likely
put 'too much fat on the fire':

def deadThings1(self,type=None):
  return [ thing for thing in self.destroyed+self.things
      if thing in self.destroyed or thing.destroyed and ... ]

this would be a seriously unnatural approach -- if you
want to return the concatenation of an existing list with
some subset of another one, expressing it as a concatenation
(of the existing list with a comprehension) is a serious
readability win over expressing it as one huge comprehension:

def deadThings2(self,type=None):
  return self.destroyed + [ thing for thing in self.things
      if thing.destroyed and ... ]

(besides, the form used in deadThings1 would have
different semantics if repetitions are allowed in
the lists, AND would be _seriously_ slower too!).

So the only remaining problem is how to express the
'...' part.  My preference would be for a local
named function -- names sometimes clarify things
a lot...:

def deadThings3(self,type=None):
    if type:
        def isDestroyed(thing, type=type):
            return thing.destroyed and isinstance(thing,type)
    else:
        def isDestroyed(thing):
            return thing.destroyed
    return self.destroyed + [ thing for thing in self.things
        if isDestroyed(thing) ]

I'd rather test 'if type' once, choosing the appropriate
selection function, than test it every time through the
loop, given it's not changing inside the loop anyway; not
so much an issue of performance, but of clarity and
simplicity.  But if you disagree, it's easy to put the
test for type in a single function rather than use it
to choose the right function:

def deadThings4(self,type=None):
    def isDestroyed(thing, type=type):
        return thing.destroyed and (
            not type or isinstance(thing,type))
    return self.destroyed + [ thing for thing in self.things
        if isDestroyed(thing) ]

If you're allergic to local named function, you _can_
expand this form if 'isDestroyed' right in the list
comprehension, though I'd say this makes things a
bit more obscured (an epsilon of clarity may be gained
through the idiom 'if X if Y or Z' rather than
'if X and (Y or Z)', though that's debatable...):

def deadThings5(self,type=None):
    return self.destroyed + [ thing for thing in self.things
        if thing.destroyed if not type or isinstance(type,thing)]

Fanatics of conciseness at all costs may prefer this
last form (which CAN be considered a single-liner, if
your terminal has long-enough lines), but my personal
tastes favour form number 3, above, or some variation
emphasizing slightly different factoring:

def deadThings6(self,type=None):
    if type:
        def inType(thing, type=type):
            return isinstance(thing,type)
    else:
        def inType(thing):
            return 1
    return self.destroyed + [ thing for thing in self.things
        if thing.destroyed and inType(thing) ]

(which I find clearer, but that's just me), or:

def deadThings7(self,type=None):
    def inType(thing, type=type):
        return not type or isinstance(thing,type)
    return self.destroyed + [ thing for thing in self.things
        if thing.destroyed and inType(thing) ]

which, I suspect, would be the first form to 'flow off
the keyboard' for most of us:-).


Alex






More information about the Python-list mailing list