10.3. Operator Comparison

  • == - eq

  • != - ne

  • < - lt

  • <= - le

  • > - gt

  • >= - ge

  • in - contains

10.3.1. SetUp

>>> from functools import reduce

10.3.2. About

Table 10.2. Comparison Operator Overload

Operator

Method

obj == other

obj.__eq__(other)

obj != other

obj.__ne__(other)

obj < other

obj.__lt__(other)

obj <= other

obj.__le__(other)

obj > other

obj.__gt__(other)

obj >= other

obj.__ge__(other)

other in obj

obj.__contains__(other)

10.3.3. Object Equality

  • When you compare objects with the same fields from two different classes

  • Always remember to compare classes

  • This way you avoid bug, when both has the same fields and values

  • Eq Works at Both Sides

>>> class Fruit:
...     def __init__(self, name):
...         self.name = name
>>>
>>>
>>> a = Fruit('Apple')
>>> b = Fruit('Apple')
>>>
>>> a == b
False
>>> class Fruit:
...     def __init__(self, name):
...         self.name = name
...
...     def __eq__(self, other):
...         return self.name == other.name
>>>
>>>
>>> a = Fruit('Apple')
>>> b = Fruit('Apple')
>>>
>>> a == b
True
>>> class Fruit:
...     def __init__(self, name):
...         self.name = name
...
...     def __eq__(self, other):
...         return self.name == other.name
>>>
>>>
>>> class Company:
...     def __init__(self, name):
...         self.name = name
>>>
>>>
>>> a = Fruit('Apple')
>>> b = Fruit('Apple')
>>> c = Company('Apple')
>>>
>>> a == b
True
>>>
>>> a == c
True
>>> class Fruit:
...     def __init__(self, name):
...         self.name = name
...
...     def __eq__(self, other):
...         return self.__class__ is other.__class__ \
...            and self.name == other.name
>>>
>>>
>>> class Company:
...     def __init__(self, name):
...         self.name = name
>>>
>>>
>>> a = Fruit('Apple')
>>> b = Fruit('Apple')
>>> c = Company('Apple')
>>>
>>> a == b
True
>>>
>>> a == c
False

Eq Works at Both Sides:

>>> class Fruit:
...     def __init__(self, name):
...         self.name = name
>>>
>>>
>>> class Company:
...     def __init__(self, name):
...         self.name = name
...
...     def __eq__(self, other):
...         return self.__class__ is other.__class__ \
...            and self.name == other.name
>>>
>>>
>>> a = Fruit('Apple')
>>> b = Fruit('Apple')
>>> c = Company('Apple')
>>>
>>> a == b
False
>>>
>>> a == c
False

10.3.4. Use Case - 0x01

  • Game

>>> hero < Damage(20)  
>>> hero > Damage(20)  

10.3.5. Use Case - 0x02

  • Numpy

SetUp:

>>> import numpy as np
>>> from pprint import pprint

Python does not support element-wise comparison:

>>> data = [[1, 2, 3],
...         [4, 5, 6],
...         [7, 8, 9]]
>>>
>>>
>>> data > 2
Traceback (most recent call last):
TypeError: '>' not supported between instances of 'list' and 'int'

In Python you have to iterate over each row and each element in a row, then to create a temporary row-result to which you append the information, at the end, you append this temporary object to result and process next line:

>>> data = [[1, 2, 3],
...         [4, 5, 6],
...         [7, 8, 9]]
>>>
>>> result = []
>>>
>>> for row in data:
...     tmp = []
...     for number in row:
...         tmp.append(number > 2)
...     result.append(tmp)
>>>
>>>
>>> pprint(result, width=30)
[[False, False, True],
 [True, True, True],
 [True, True, True]]

Alternatively you can use nested list comprehensions to do the same:

>>> data = [[1, 2, 3],
...         [4, 5, 6],
...         [7, 8, 9]]
>>>
>>> result = [
...     [number > 2 for number in row]
...     for row in data
... ]
>>>
>>> pprint(result, width=30)
[[False, False, True],
 [True, True, True],
 [True, True, True]]

In numpy all the comparison is being done element-wise and we do not have to worry about iteration etc. It is called vectorized operation:

>>> data = np.array([[1, 2, 3],
...                  [4, 5, 6],
...                  [7, 8, 9]])
>>>
>>> data > 2
array([[False, False,  True],
       [ True,  True,  True],
       [ True,  True,  True]])

10.3.6. Assignments

Code 10.26. Solution
"""
* Assignment: Operator Comparison Equals
* Complexity: easy
* Lines of code: 3 lines
* Time: 5 min

English:
    1. Override operator for code to work correctly
    2. Do not use `dataclasses`
    3. Run doctests - all must succeed

Polish:
    1. Nadpisz operator aby poniższy kod zadziałał poprawnie
    2. Nie używaj `dataclasses`
    3. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0

    >>> Mission(2035, 'Ares 3') == Mission(2035, 'Ares 3')
    True
    >>> Mission(2035, 'Ares 3') == Mission(1973, 'Apollo 18')
    False
    >>> Mission(2035, 'Ares 3') == Mission(2035, 'Apollo 18')
    False
    >>> Mission(2035, 'Ares 3') == Mission(1973, 'Ares 3')
    False
"""

class Mission:
    def __init__(self, year, name):
        self.year = year
        self.name = name


Code 10.27. Solution
"""
* Assignment: Operator Comparison Contains
* Complexity: easy
* Lines of code: 5 lines
* Time: 8 min

English:
    1. Override operators for code to work correctly
    2. Do not use `dataclasses`
    3. Run doctests - all must succeed

Polish:
    1. Nadpisz operatory aby poniższy kod zadziałał poprawnie
    2. Nie używaj `dataclasses`
    3. Uruchom doctesty - wszystkie muszą się powieść

Hints:
    * `object.__contains__()`
    * `object.__eq__()`

Tests:
    >>> import sys; sys.tracebacklimit = 0

    >>> astro = Astronaut(firstname='Mark', lastname='Watney', missions=[
    ...     Mission(1969, 'Apollo 11'),
    ...     Mission(2024, 'Artemis 3'),
    ...     Mission(2035, 'Ares 3'),
    ... ])

    >>> Mission(2035, 'Ares 3') == Mission(2035, 'Ares 3')
    True
    >>> Mission(2035, 'Ares 3') == Mission(1973, 'Apollo 18')
    False
    >>> Mission(2035, 'Ares 3') == Mission(2035, 'Apollo 18')
    False
    >>> Mission(2035, 'Ares 3') == Mission(1973, 'Ares 3')
    False

    >>> Mission(2024, 'Artemis 3') in astro
    True
    >>> Mission(1973, 'Apollo 18') in astro
    False
"""


class Mission:
    year: int
    name: str

    def __init__(self, year: int, name: str) -> None:
        self.year = year
        self.name = name


class Astronaut:
    firstname: str
    lastname: str
    missions: list

    def __init__(self, firstname: str, lastname: str, missions: list) -> None:
        self.firstname = firstname
        self.lastname = lastname
        self.missions = missions