nested for and if inside a list comprehension

Viewed 60

I have the following code which has for and if alternatively.

lines = ['apple berry Citrus ', 34, 4.46, 'Audi Apple ']

corpus = [ ]

for line in lines:

    # Check if the element is string and proceed
    if isinstance(line, str):

        # Split the element and check if first character is upper case
        for word in line.split():
            if word[0].isupper():
                
                # Append the word to corpus result
                corpus.append(word)
print(corpus)
# Output : ['Citrus', 'Audi', 'Apple']

I am trying to do this in list comprehension but failing. I have tried as below.

# corpus = [  word if word[0].isupper() for word in line.split()  for line in lines if isinstance(line, str)]

How can i achieve this in List Comprehension ?

3 Answers

The following will work:

corpus = [
    word for line in lines if isinstance(line, str) 
         for word in line.split() if word[0].isupper()
]

The scope in nested comprehensions can be confusing at first, but you'll notice that the order of for and if is the same as in the nested loops.

You can do:

[word for line in lines if isinstance(line, str) 
     for word in line.split() if word[0].isupper()]

An alternate approach is to prefilter the list lines with filter:

[word for line in filter(lambda e: isinstance(e, str), lines) 
      for word in line.split() if word[0].isupper()]

Or, as pointed out in comments, you can eliminate the lambda with:

[word for line in filter(str.__instancecheck__, lines) 
      for word in line.split() if word[0].isupper()]

Or even two filters:

[word for line in filter(str.__instancecheck__, lines)
      for word in filter(lambda w: w[0].isupper(), line.split())]

As a general rule, you can take the inner part of the nested loops where you do corpus.append(xxxx) and place it (the xxx) at the beginning of the list comprehension. Then add the nested for loops and conditions without the ':'.

corpus = [ word                          # from corpus.append(word)
           for line in lines             # ':' removed ... 
               if isinstance(line,str) 
                   for word in line.split() 
                       if word[0].isupper() ]

# on a single line:
corpus = [ word for line in lines if isinstance(line,str) for word in line.split() if word[0].isupper() ]

print(corpus) # ['Citrus', 'Audi', 'Apple']
Related