tags:

views:

693

answers:

5

Is there a Pythonic way to check if a list (a nested list with elements & lists) is essentially empty? What I mean by empty here is that the list might have elements, but those are also empty lists.

The Pythonic way to check an empty list works only on a flat list:

alist = []
if not alist:
    print("Empty list!")

For example, all the following lists should be positive for emptiness:

alist = []
blist = [alist]               # [[]]
clist = [alist, alist, alist] # [[], [], []]
dlist = [blist]               # [[[]]]
+2  A: 

I don't think there is an obvious way to do it in Python. My best guess would be to use a recursive function like this one :

def empty(li):
    if li == []:
        return True
    else:
        return all((isinstance(sli, list) and empty(sli)) for sli in li)

Note that all only comes with Python >= 2.5, and that it will not handle infinitely recursive lists (for example, a = []; a.append(a)).

Pierre Bourdon
Nice, I've never thought about infinite recursive lists in Python. It's cool to see that they can even be printed ;-)
THC4k
+1  A: 

A simple recursive check would be enough, and return as early as possible, we assume it input is not a list or contains non-lists it is not empty

def isEmpty(alist):
    try:
        for a in alist:
            if not isEmpty(a):
                return False
    except:
        # we will reach here if alist is not a iterator/list
        return False

    return True

alist = []
blist = [alist]               # [[]]
clist = [alist, alist, alist] # [[], [], []]
dlist = [blist]               # [[[]]]
elist = [1, isEmpty, dlist]

if isEmpty(alist): 
    print "alist is empty"

if isEmpty(dlist): 
    print "dlist is empty"

if not isEmpty(elist): 
    print "elist is not empty"

You can further improve it to check for recursive list or no list objects, or may be empty dicts etc.

Anurag Uniyal
`isEmpty([1])` fails, that's quite a problem (it's not really useful to have a function to check for an empty list if it only works with lists you know to be empty).
Pierre Bourdon
that could be a good logic to check if list is not empty :)
Anurag Uniyal
DelRoth: Anurag seems to have now fixed the problem you commented now.
Ashwin
+4  A: 

Simple code, works for any iterable object, not just lists:

>>> def empty(seq):
...     try:
...         return all(map(empty, seq))
...     except TypeError:
...         return False
...
>>> empty([])
True
>>> empty([4])
False
>>> empty([[]])
True
>>> empty([[], []])
True
>>> empty([[], [8]])
False
>>> empty([[], (False for _ in range(0))])
True
>>> empty([[], (False for _ in range(1))])
False
>>> empty([[], (True for _ in range(1))])
False

This code makes the assumption that anything that can be iterated over will contain other elements, and should not be considered a leaf in the "tree". If an attempt to iterate over an object fails, then it is not a sequence, and hence certainly not an empty sequence (thus False is returned). Finally, this code makes use of the fact that all returns True if its argument is an empty sequence.

Stephan202
Catching all exception is a bad thing that may cause to hiding real errors in the code.
Denis Otkidach
@Denis: a valid point. Now narrowed it down to `TypeError`.
Stephan202
+1 but I think `map` shouldn't be used in Python. `all( empty(x) for x in seq )` sounds much nicer to me ;-)
THC4k
+3  A: 

If you do not need to iterate through the lists, simpler is better, so something like this will work:

def empty_tree(input_list):
    """Recursively iterate through values in nested lists."""
    for item in input_list:
        if not isinstance(item, list) or not empty_tree(item):
             return False
    return True

However, it would be good to separate the recursive iteration that you will most probably reuse elsewhere and checking that it returns no elements. This way if the mechanism of iteration changes you need to implement the change in one place. For instance when you need to support arbitrary nested iterables, or nested dicts.

def flatten(input_list):
    """Recursively iterate through values in nested lists."""
    for item in input_list:
        if isinstance(item, list): # Use what ever nesting condition you need here
            for child_item in flatten(item):
                yield child_item
        else:
            yield item

def has_items(seq):
    """Checks if an iterator has any items."""
    return any(1 for _ in seq)

if not has_items(flatten(my_list)):
    pass
Ants Aasma
Ants Asama: I like the simplicity of your solution the best! I have edited your answer and moved the simple solution up. Thanks! :-)
Ashwin
+1  A: 

I have combined the use of isinstance() by Ants Aasma and all(map()) by Stephan202, to form the following solution. all([]) returns True and the function relies on this behaviour. I think it has the best of both and is better since it does not rely on the TypeError exception.

def isListEmpty(inList):
    if isinstance(inList, list): # Is a list
        return all( map(isListEmpty, inList) )
    return False # Not a list
Ashwin
`return all(map(isListEmpty, inList)) if isinstance(inList, list) else False` :)
Stephan202
Stephan202: Yup, that turns it into a real one-liner! I am just not that comfortable with this Python conditional expression though. A bit confusing since it is not ordered in the same way as the C ternary operator ;-)
Ashwin