.. _Logical_Operations: ****************** Logical Operations ****************** The Identity Operator ===================== Since all Python variables are actually 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:: >>> enz_1 = ("EcoR1", "Ecoli restriction enzyme I", "gaattc", 1, "sticky") >>> enz_2 = ("EcoR1", "Ecoli restriction enzyme I", "gaattc", 1, "sticky") >>> id(enz_1) 140454188035336 >>> id(enz_2) 140454188035240 >>> enz_1 is enz_2 False >>> enz_1 == enz_2 True >>> enz_2 = enz_1 >>> enz_1 is enz_2 True Note that it usually does not make sense to use ``is`` for comparing ints, strings, 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 ``enz_1`` and ``enz_2`` are initially set to the same named tuple value, the named tuples themselves are held as separate objects and ``is`` therefore returns ``False`` the first time we use it. One benefit of identity comparison is that it is a very fast operation. The reason is that 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 * ``>``: 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 interactive interpreter:: >>> a = 2 >>> b = 6 >>> a == b False >>> a < b True >>> a <= b, a != b, a >= b, a > b (True, True, False, False) Everything is as we would expect with integers. Strings are compared using lexicographic order (for normal letters, that's the alphabetical order):: >>> a = "many paths" >>> b = "many paths" >>> a == b True >>> c = "mary patches" >>> a < c True >>> d = "harry snatches" >>> a < d False .. warning:: Be aware, though, that because Python 3 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. .. warning:: In some cases, comparing the identity of two strings or numbers, for instance, 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 True 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): File "", line 1, in TypeError: '<' not supported between instances of 'str' and 'int' 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 instance:: >>> p = (4, "frog", 9, -33, 9, 2) >>> 2 in p True >>> "dog" not in p True 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 True >>> "han" in phrase True Conveniently, in the case of strings, the membership operator can be used to test for substrings of any length. (A character is just a string of length ``1``.) Boolean Operators ================= Python provides the three main operators of Boolean logic: ``and``, ``or``, and ``not``. They behave very logically indeed:: >>> True and False False >>> True or False True >>> not False True Both ``and`` and ``or`` use short-circuit logic. This means that they only evaluate what is necessary to determine the result (starting at the left operand):: >>> True or (1 / 0) True >>> False or (1 / 0) Traceback (most recent call last): File "", line 1, in ZeroDivisionError: division by zero In an expression like ``x or y``, if ``x`` evaluates to ``True``, the whole expression will also evaluate to ``True``. It is therefore not necessary to evaluate the right operand in such a case. However, if ``x`` evaluates to ``False``, the result will depend on the value of ``y``. In the above experiment, the evaluation of the right operand did only happen in the second case, as revealed by the ``ZeroDivisionError`` exception. The same kind of reason explain the following behaviour:: >>> False and (1 / 0) False >>> True and (1 / 0) Traceback (most recent call last): File "", line 1, in ZeroDivisionError: division by zero As soon as we know that the first operand of an ``and`` expression evaluates to ``False``, we know that the whole expression will evaluate to ``False``. The evaluation of the right operand is therefore short-circuited in the first case. When using non Boolean operands, the result is the operand that determined the result, not its conversion into a Boolean. Let's see how ``and`` behaves:: >>> five = 5 >>> two = 2 >>> zero = 0 >>> five and two 2 >>> two and five 5 >>> five and zero 0 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. Now let's test ``or``:: >>> nought = 0 >>> five or two 5 >>> two or five 2 >>> zero or five 5 >>> zero or nought 0 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:: >>> not (zero or nought) True >>> not two False