Question

I want to get the only Non-None element from this list:

L = [None, [None,None], [None, <__main__.Car object at 0x02A11550>], [None, None, None], None]

I've tried

L = [x for x in L if x is not None]

But the result is

[[None, None], [None, <__main__.Car object at 0x02A11550>], [None, None, None]]

Deleting only the None that are not inside of any list. Is there any way to clean the whole list? so the output is

<__main__.Car object at 0x02A11550>
Was it helpful?

Solution

def flatten(lst):
    for element in lst:
        if hasattr(element,"__iter__"):
            yield from flatten(element)
        elif not element is None:
            yield element

new_list = flatten(L)

I'll break this down for you, first starting with generators. The yield keyword is sister to return, but with much different functionality. Both are used to bring values out of a function into its calling scope, but yield allows you to jump back into the function afterwards! As an example, below is a generator that accepts a list full of numbers and produces the square for each number in the list.

def example_generator(number_list):
    for number in number_list:
        yield number**2

>>> gen = example_generator([1,2,3])
>>> type(gen)
<class 'generator'>
>>> next(gen) # next() is used to get the next value from an iterator
1
>>> next(gen)
4
>>> next(gen)
9
>>> next(gen)
Traceback (most recent call last):
  File "<pyshell#6>", line 1, in <module>
    next(gen)
StopIteration

Generators are one-time use, however. As you can see, after I reached the end of the generator, it threw an exception StopIteration. If I built it again and ran through it with a loop, then tried to run through it AGAIN...

>>> gen = example_generator([1,2,3]) # remember this is a new generator, we JUST made it
>>> for item in gen:
...     print(item)
1
4
9
>>> for item in gen:
...     print(item)
>>>

It doesn't do anything the second time. The generator is exhausted. That's the downside -- the upside is that it's generally much faster and more memory-efficient to use generators instead of lists.

yield also allows you to use another keyword: from. That's what I did there in case of a nested list (hasattr(element,"__iter__") just means that the element has an attribute .__iter__, which means it can be iterated upon using something like a for loop). You give yield from another generator, and it yields each element from THAT generator in turn. For example:

def flatten_lite(lst):
    for element in lst:
        if type(element) is list: # more readable, IMO
            yield from flatten_lite(element)
        else:
            yield element

a = flatten_lite([1,2,3,[4,5,6,[7],8],9])

Here's what it does in turn:

for element in [1,2,3,[4,5,6,[7],8],9]:
    # element == 1
    if element is of type list: # it's not, skip this
    else: yield element # which is 1
    :: NEXT ITERATION ::
    # element == 2, same as before
    :: NEXT ITERATION ::
    # element == 3, same as before
    :: NEXT ITERATION ::
    # element == [4,5,6,[7],8]
    if element is of type list: # it is!!
        yield from flatten_lite([4,5,6,[7],8])
        :: STOP EXECUTION UNTIL WE GET A VALUE FROM THAT NEW GENERATOR ::
>>> NEW GENERATOR
for element in [4,5,6,[7],8]:
    # element is 4
    yield 4
        :: THE OUTER GENERATOR YIELDS 4 ::
    :: NEXT ITERATION ::
    # element is 5
    yield 5
        :: THE OUTER GENERATOR YIELDS 4 ::
    :: NEXT ITERATION ::
    # element is 6
    yield 6
        :: THE OUTER GENERATOR YIELDS 4 ::
    :: NEXT ITERATION ::
    # element is [7]
    if element is of type list # [7] is a list!
        yield from flatten_lite([7])
            :: STOP EXECUTION UNTIL WE GET A VALUE FROM THAT NEW GENERATOR ::
            # etc etc

So basically the code above says (in pseudocode):

flatten is a function that accepts parameter: lst
    for each element in lst:
        if element can be iterated on:
            yield every element in turn from the generator created
              by this function called on the element instead of the
              main list
        if it's not, and isn't None:
            yield element

When you call it, it builds a generator that can be iterated upon. To make it into a formal list, you'll have to do list(flatten(L)), but in most cases you don't need that.

Is that any clearer?

OTHER TIPS

Another slightly more modular approach:

def flatten(l):
    """ http://stackoverflow.com/questions/2158395/flatten-an-irregular-list-of-lists-in-python/2158532#2158532 """
    for el in l:
        if isinstance(el, collections.Iterable) and not isinstance(el, basestring):
            for sub in flatten(el):
                yield sub
        else:
            yield el

filter(None,flatten(L)) #wrap with `list` in python 3.x

A generic flatten function is something you should keep in your toolbox, since (so far) it's not something you can find in the standard library, and it comes up occasionally.

Just for fun, how about:

from itertools import chain, ifilterfalse
result = list(ifilterfalse(lambda x: x is None, chain(*[x for x in L if x is not None])))

This will return a list with only the Car element present. It will generalize to return a list with any non-None element.

In Python 3.x I think you swap ifilterfalse for filterfalse and it works the same.

chain() is designed to flatten a list of lists for iteration. ifilterfalse can work directly on the chain returned. ifilterfalse gets rid of elements that match the predicate specified by the lambda function.

Note that if you have strings in L, chain() will essentially break the strings up into individual elements. If that is a problem for you, see this other SO post.

Another implementation which avoids the problem of non-iterables at the base level:

result = list(ifilterfalse(lambda x: x is None, chain(*[x if hasattr(x, '__iter__') else [x] for x in L if x is not None])))

I'm told this may not work in Python 3 because of how str is implemented there. Anyway I'm only posting these ideas so that you are aware of functionality already available in the Python standard library under itertools. Have fun learning Python!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top