The other day on Reddit someone asked this:

Why does all() return True if the iterable is empty? Shouldn’t it return False just like if my_list: would evaluate to False if the list is empty? What’s the thinking behind it returning True?

They have since removed their question, but it sparked a long discussion thread, and my comment was heavily upvoted, so I thought I would record it here:

This is literally a 2,500 year old debate in philosophy. The ancients thought “all unicorns are blue” should be false because there are no unicorns, but modern logic says it is true because there are no unicorns that aren’t blue. Python is just siding with modern predicate logic, but your intuition is also quite common and was the orthodox position until the last few hundred years.

Here is a little more explanation. Western logic is generally thought of as having two main periods: Aristotelean syllogistic logic and modern predicate logic. The classic Aristotelean syllogism runs along the lines of:

  1. Socrates is a man.
  2. All men are mortal.
  3. Therefore, Socrates is mortal.

Logic in the Aristotelean system is centered around statements that all, some, or no X is Y. This creates a square of opposition in which “all men are mortal” contradicts “some men are not mortal”, and “Some men are just” contradicts “no man is just.” Peter Abelard (12c.) pointed out a problem with this system: “All stonemen are made of stone” is true by definition, but “no men are made of store” is also true and seems to contradict it.

Debate about this topic raged without every really being settled. Instead, a new system of logic arose in the nineteenth century that replaced the old one. In predicate logic, Fa means “a is an F” and ∀x Fx “all x are Fs” while ∃x Fx means “there exists an x which is an F". In this system, there is no contradiction between “all x are F” and “there does not exist an x which is F” when there is no x. Because the new logic only talks about individuals and predicates rather than about universals and essences, it has no problem with saying “all stonemen are made of bologna”, since there are no stonemen, so no predicates apply. On the other hand, in the new logic, you can’t soundly argue that “all dogs are mammals; all mammals have fur; therefore, all dogs have fur” because one person shaving their dog suddenly makes “all mammals have fur” untrue.

Several commenters on Reddit thought that the problem with all([]) was that we were restricting ourselves to either true or false answers. In logic, this is called the principle of bivalence: there are only two values that a proposition can have, true or false. But Python has None, many other programming languages have null or nil, and Zen has mu. Maybe adding another value would help?

On one level, the principle of bivalence clearly does not apply to all speech, because not all speech is “truth apt”. If I say, “Go Eagles!” I can mean it seriously or sarcastically, but I can’t be said to be saying something true or false. Logic is only supposed to be “truth preserving” for “propositions”: speech acts that are truth apt. However, many-valued logics go further and try to mathematically define the ways in which we can preserve desired properties (“truth” or perhaps “justification”) through arguments. This is still an area of active research and no system of many valued logic has yet become standard.

For workaday Pythonistas, this basically doesn’t matter. The Python core team chose to make all([]) return True, and whatever their reasons, you can program your way around by adding wrapper functions or if tests. But it is interesting how even extremely practical twenty-first century programming can get drawn into millennia old philosophical controversies, intentionally or not.