Logical Operations

The Identity Operator

Since all Python variables are really object references, it sometimes makes sense to ask whether two or more object references are referring to the same object. The is operator is a binary operator that returns True if its left-hand object reference is referring to the same object as its right-hand object reference. Here are some examples:

>>> import collections
>>> RestrictEnzyme = collections.namedtuple("RestrictEnzyme", "name comment sequence cut end")
>>> enz_1 = RestrictEnzyme("EcoR1", "Ecoli restriction enzime I", "gaattc", 1, "sticky")
>>> enz_2 = RestrictEnzyme("EcoR1", "Ecoli restriction enzime I", "gaattc", 1, "sticky")
>>> id(enz_1)
>>> id(enz_2)
>>> enz_1 is enz_2
>>> enz_1 == enz_2
>>> enz_2 = enz_1
>>> enz_1 is enz_2
>>> a

Note that it usually does not make sense to use is for comparing ints, strs, and most other data types since we almost invariably want to compare their values. In fact, using is to compare data items can lead to unintuitive results, as we can see in the preceding example, where although a and b are initially set to the same list values, the lists themselves are held as separate list objects and so is returns False the first time we use it. One benefit of identity comparisons is that they are very fast. This is because the objects referred to do not have to be examined themselves. The is operator needs to compare only the memory addresses of the objects the same address means the same object. The most common use case for is is to compare a data item with the built-in null object, None , which is often used as a place-marking value to signify “unknown” or “nonexistent”:

>>> a = "Something"
>>> b = None
>>> a is not None, b is None
(True, True)

To invert the identity test we use is not . The purpose of the identity operator is to see whether two object references refer to the same object, or to see whether an object is None . If we want to compare object values we should use a comparison operator instead.

Comparison Operators

Python provides the standard set of binary comparison operators, with the expected semantics: < less than, <= less than or equal to, == equal to, != not equal to, >= greater than or equal to, and > greater than. These operators compare object values, that is, the objects that the object references used in the comparison refer to. Here are a few examples typed into a Python Shell:

>>> a = 2
>>> b = 6
>>> a == b
>>> a < b
>>> a <= b, a != b, a >= b, a > b
(True, True, False, False)

Everything is as we would expect with integers. Similarly, strings appear to compare properly too:

>>> a = "many paths"
>>> b = "many paths"
>>> a is b
>>> a == b

Although a and b are different objects (have different identities), they have the same values, so they compare equal.


Be aware, though, that because Python3 uses Unicode for representing strings, comparing strings that contain non-ASCII characters can be a lot subtler and more complicated than it might at first appear


In some cases, comparing the identity of two strings or numbers—for example, using a is b will return True , even if each has been assigned separately as we did here. This is because some implementations of Python will reuse the same object (since the value is the same and the data type is immutable) for the sake of efficiency. The moral of this is to use == and != when comparing values, and to use is and is not only when comparing with None or when we really do want to see if two object references, rather than their values, are the same.

One particularly nice feature of Python’s comparison operators is that they can be chained. For example:

>>> a = 9
>>> 0 <= a <= 10

This is a nicer way of testing that a given data item is in range than having to do two separate comparisons joined by logical and , as most other languages require. It also has the additional virtue of evaluating the data item only once (since it appears once only in the expression), something that could make a difference if computing the data item’s value is expensive, or if accessing the data item causes side effects.

Thanks to the “strong” aspect of Python’s dynamic typing, comparisons that don’t make sense will cause an exception to be raised. For example:

>>> "three" < 4
Traceback (most recent call last):
TypeError: unorderable types: str() < int()

When an exception is raised and not handled, Python outputs a traceback along with the exception’s error message. For clarity, we have omitted the traceback part of the output, replacing it with an ellipsis. The same TypeError exception would occur if we wrote “3” < 4 because Python does not try to guess our intentions, the right approach is either to explicitly convert, for example, int(“3”) < 4 , or to use comparable types, that is, both integers or both strings. Python makes it easy for us to create custom data types that will integrate nicely so that, for example, we could create our own custom numeric type which would be able to participate in comparisons with the built-in int type, and with other built-in or custom numeric types, but not with strings or other non-numeric types.

The Membership Operator

For data types that are sequences or collections such as strings, lists, and tuples, we can test for membership using the in operator, and for nonmembership using the not in operator. For example:

>>> p = (4, "frog", 9, -33, 9, 2)
>>> 2 in p
>>> "dog" not in p

For lists and tuples, the in operator uses a linear search which can be slow for very large collections (tens of thousands of items or more). On the other hand, in is very fast when used on a dictionary or a set. Here is how in can be used with a string:

>>> phrase = "Wild Swans by Jung Chang"
>>> "J" in phrase
>>> "han" in phrase

Conveniently, in the case of strings, the membership operator can be used to test for substrings of any length. (As noted earlier, a character is just a string of length 1.)

Logical Operators

Python provides three logical operators: and, or, and not. Both and and or use short-circuit logic and return the operand that determined the result they do not return a Boolean (unless they actually have Boolean operands). Let’s see what this means in practice:

five = 5
two = 2
zero = 0
five and two
2 # bool(2) = True
two and five
5 # bool(5) = True
five and zero
0 # bool(0) = False

If the expression occurs in a Boolean context, the result is evaluated as a Boolean, so the preceding expressions would come out as True, True, and False in, say, an if statement.

nought = 0
five or two
two or five
zero or five
zero or nought

The or operator is similar; here the results in a Boolean context would be True, True, True, and False. The not unary operator evaluates its argument in a Boolean context and always returns a Boolean result, so to continue the earlier example, not (zero or nought) would produce True, and not two would produce False.