pythonpython-3.xlistboolean

Why Python's `all` built-in function returns `True` when checking if an empty string is in a list when it isn't?


I wrote a function that checks if every character on a given string is also present in an arbitrary list. The code works for most cases, but when the input is an empty string, the built-in function all evaluates to True, even though that value isn't present at the ITEMS list:

ITEMS = ['a', 'b', 'c', 'd']

def check_all_chars_are_in_list(string):
    return all(char in ITEMS for char in string)


if __name__ == "__main__":
    print(check_all_chars_are_in_list('saban'))   # False, as expected. (1)
    print(check_all_chars_are_in_list('abcd'))    # True, as expected.  (2)
    print(check_all_chars_are_in_list(''))        # True (why?).        (3) 

I performed a manual test as to try and understand that behavior. I simply checked the in assertion via the terminal:

>>> st = ''
>>> l = ['a', 'b', 'c', 'd']
>>> st in l
False        # As expected

But I got the expected False value, so how come the example (3) on my code evaluates to True? Can someone explain that to me, please?

Thank you!


Solution

  • First of all, what does function all do? According to docs:

    Return True if all elements of the iterable are true (or if the iterable is empty). Equivalent to:

    def all(iterable):
        for element in iterable:
            if not element:
                 return False
        return True
    

    That means that you have to pass iterable of boolean values to function all.

    Let's walk through your examples to see what happens. I will modify your function a little just so it prints list that is passed to function all to see what is really passed.

    ITEMS = ['a', 'b', 'c', 'd']
    
    def check_all_chars_are_in_list(string):
        temp = [char in ITEMS for char in string]
        print(temp)
        return all(temp)
    

    So, for your examples:

    check_all_chars_are_in_list('saban') # list temp is [False, True, True, True, False]
    check_all_chars_are_in_list('abcd') # list temp is [True, True, True, True]
    check_all_chars_are_in_list('') # list temp is [] (empty)
    

    What happens in third example is that you pass empty list to function all and by definition it returns True. Why is temp = [] in that example? Because you are trying to loop through string (which is empty) in list comprehension and since it has nothing to loop through it sets temp to be empty list. If you look at to what is function all equivalent (provided in quoted docs) you can see that when you pass empty list it will have nothing to loop through so it will just return True.

    Your terminal example is not really the same as the one using function all. Lets see what might be equivalent to checking if string is in list. What your code:

    st = ''
    l = ['a', 'b', 'c', 'd']
    print(st in l)
    

    is equivalent (not exactly, but logically) is this:

    def check_with_in(string, l):
        for el in l:
            if el == string:
                return True
        return False
    
    print(check_with_in('', ['a', 'b', 'c', 'd'])) # False
    

    and since '' is not in the list it will return False. What using in with lists is equivalent to according to docs:

    The operators in and not in test for membership. [...] For container types such as list, tuple, set, frozenset, dict, or collections.deque, the expression x in y is equivalent to any(x is e or x == e for e in y).