This article is from the “Why Python” series

Python’s syntax is simple when it comes to Truth Value Testing.

For example, to determine whether an object is not None, or whether a container object is not empty, you don’t need to explicitly write the criteria. You just need to write the object directly after the if or while keyword.

Here’s an example of a list. The short if my_list expression has two meanings:

If you need to do the opposite, “If None or empty,” just write if not my_list.

A different way of judging truth values

In general, when a value is itself a Boolean, it makes sense to write “if XXX “(if true). It is not semantically intelligible to write “if XXX” (if something) if XXX is not itself a Boolean.

In static languages such as C/C++/Java, it is common to do a comparison based on XXX, such as “if (XXX == null)”, to get the result of a Boolean value, and then to make a truth judgment. Otherwise, if “if XXX” contains a non-Boolean value, a type error will be reported.

Python, a dynamic language, shows flexibility in such scenarios, so the question is: why can Python make truth judgments on arbitrary objects without first doing a comparison?

Let’s start with the description of truth judgment in the documentation:

In short, any object in Python can be used in if or while or Boolean operations (and, OR, not), and is considered true by default unless it has a __bool__() method returnFalseOr have a __len__() method return0

For the previous example, my_list does not have a __bool__() method, but it does have a __len__() method, so whether it is true depends on the return value of that method.

Bytecode for truth determination

Next, let’s get to the bottom of the question: why does Python support such broad truth judgments? What exactly is it doing when a statement like if XXX is executed?

For the first question, Python has a built-in bool() type that converts any object to a Boolean value. So, does this mean that Python implicitly calls bool() (that is, if bool(XXX)) when making truth judgments? (The answer is no, as analyzed below)

For the second problem, you can first use the DIS module to check:

The POP_JUMP_IF_FALSE command corresponds to the if statement line, which means:

If TOS is false, sets the bytecode counter to target. TOS is popped.

If the top element of the stack is false, jump to the target location.

There is only a description of the jump action, and you still don’t see how a normal object becomes a Boolean object.

How exactly does Python implement truth determination in the interpreter?

True value judgment source code implementation

With the help of my wechat group friend Jo, I found the source code of CPython (files: Ceval. c, object.c) :

As you can see, for Objects of Boolean type (that is, Py_True and Py_False), the code goes into the fast-processing branch; For other objects, PyObject_IsTrue() evaluates to a value of type int.

The PyObject_IsTrue() function retrievalsnb_bool, mp_length, and sq_length, which correspond to the __bool__() and __len__() magic methods.

This process is described in the official documentation cited above and is exactly what we are looking for!

PyObject_IsTrue() ¶ BoolObject.c: BoolObject_istrue (); boolObject.c: BoolObject_istrue ()

So, Python does not implicitly call bool() when making truth checks on ordinary objects. Instead, it calls a separate function (PyObject_IsTrue()), which is used by bool().

In other words, bool() and if/while statements use essentially the same processing logic to determine the truth value of ordinary objects. If bool(XXX) is redundant (I’ve seen it before).

So far, we have answered the questions raised in the previous article.

The process of verifying truth judgments

Next, there are three test examples that can be further verified:

You can pause and think: what is the result of bool(Test1) versus bool(Test1())? And then what happens to the remaining two classes?

Reveal the answer:

bool(Test1)    # True
bool(Test2)    # True
bool(Test3)    # True

bool(Test1())  # True
bool(Test2())  # False
bool(Test3())  # True
Copy the code

Here’s why:

  • Bool () does not call its __bool__() or __len__() magic methods when the class object is not instantiated
  • Bool () calls __bool__() first if both __bool__() and __len__() magic methods are present. This method requires that the value returned be a bool, so there is no need to use __len__() to determine true or false.)

How do numeric types make truth judgment?

In addition to these three examples, there is another case worth checking, which is how they do truth judgments for numeric types.

We can verify that numeric types have those two magic methods:

hasattr(2020."__bool__")
hasattr(2020."__len__")
Copy the code

It’s not hard to verify that numbers have a __bool__() magic method, not a __len__() magic method, and all types of numbers are actually divided into two categories:

  • __bool__()Return False: all numbers representing 0, for example0.0.0.0j.Decimal(0).Fraction(0, 1)
  • __bool__()Returns True: all other non-zero numbers

The article summary

The easy way to write if XXX in Python is normal truth checking syntax, but it does not conform to normal semantics. In languages like C/C++/Java, XXX is either a Boolean value itself or an operation that returns a Boolean value, but in Python, XXX can be any Python object!

In this article, a step-by-step analysis of the documentation, bytecode, and CPython interpreter source code shows that Python’s truth determination process is not simple, which can be summarized as follows:

  • If /while are implicit Boolean operators: in addition to “judging” true or false, they implicitly evaluate common objects to Boolean results. The actual operation is done by the interpreter according to the “POP_JUMP_IF_FALSE” command, whose core logic shares an underlying method with the built-in bool()
  • The truth determination process relies on two magic methods:Unless the judged object has a __bool__() method returnFalseOr have a __len__() method return0Otherwise the Boolean operation returns True. Both magic methods always evaluate __bool__() first
  • Numeric types can also do truth checks: numbers have a __bool__() magic method, but no __len__() magic method, all numbers except 0 are False

If you think this analysis is good, you will enjoy these articles:

1. Why does Python use indentation to divide code blocks?

2. Is Python’s indentation anti-human?

3. Why does Python use semicolons as statement terminators?

4. Why does Python have a main function? Why don’t I recommend main?

5. Why does Python recommend serpentine nomenclature?

6. Why doesn’t Python support the i++ increment syntax and the ++ operator?

7. Why can Python exchange two variables directly with a single statement: “a,b=b,a”?

8. Why does Python use # as a comment?

Why does Python have a pass statement?

10. Why does Python have a strange “…” Objects?

This article is part of the “Why Python” series, which focuses on the syntax, design, and development of Python. The series tries to show the charm of Python by asking “why” questions. All posts will be archived on Github at github.com/chinesehuaz…