На этом шаге мы рассмотрим возможности декоратора functools.total_ordering.
Вы бы хотели сравнивать экземпляры вашего класса, используя обычные операторы сравнения (т. е. >=, !=, <= и т. д.), но без написания большого количества специальных методов.
Классы Python могут поддерживать сравнение путем реализации специального метода для каждого из операторов сравнения. Например, чтобы обеспечить поддержку оператора >=, вам нужно определить в классах метод __ge__(). Хотя определение одного метода обычно не является проблемой, реализация методов для всех возможных операторов сравнения может быстро надоесть.
Декоратор functools.total_ordering может помочь упростить этот процесс. Вы декорируете им класс и определяете метод __eq_(), а также один из методов сравнения (__lt_(), __le__(), __gt__() или __ge__()). Затем декоратор заполнит другие методы за вас.
В качестве примера давайте построим несколько домов и добавим в них несколько комнат, а затем выполним сравнения:
>>> from functools import total_ordering >>> class Room: def __init__(self, name, length, width): self.name = name self.length = length self.width = width self.square_feet = self.length * self.width >>> @total_ordering class House: def __init__(self, name, style): self.name = name self.style = style self.rooms = list() @property def living_space_footage(self): return sum(r.square_feet for r in self.rooms) def add_room(self, room): self.rooms.append(room) def __str__(self): return '{}: {} square foot {}'.format(self.name, self.living_space_footage, self.style) def __eq__(self, other): return self.living_space_footage == other.living_space_footage def __lt__(self, other): return self.living_space_footage < other.living_space_footage >>>
Здесь класс House был декорирован с помощью @total_ordering. Определения __eq__() и __lt_() предоставлены для сравнения домов на основе общего метража их комнат. Это минимальное определение - все, что требуется, чтобы заставить работать другие операции сравнения. Например:
>>> # Построим несколько домов и добавим в них комнаты >>> h1 = House('h1', 'Cape') >>> h1.add_room(Room('Master Bedroom', 14, 21)) >>> h1.add_room(Room('Living Room', 18, 20)) >>> h1.add_room(Room('Kitchen', 12, 16)) >>> h1.add_room(Room('Office', 12, 12)) >>> >>> h2 = House('h2', 'Ranch') >>> h2.add_room(Room('Master Bedroom', 14, 21)) >>> h2.add_room(Room('Living Room', 18, 20)) >>> h2.add_room(Room('Kitchen', 12, 16)) >>> >>> h3 = House('h3', 'Split') >>> h3.add_room(Room('Master Bedroom', 14, 21)) >>> h3.add_room(Room('Living Room', 18, 20)) >>> h3.add_room(Room('Office', 12, 16)) >>> h3.add_room(Room('Kitchen', 15, 17)) >>> houses = [h1, h2, h3] >>> print('Is h1 bigger than h2?', h1 > h2) Is h1 bigger than h2? True >>> print("Is h2 smaller than h3?", h2 < h3) Is h2 smaller than h3? True >>> print("Is h2 greater than or equal to h1?", h2 >= h1) Is h2 greater than or equal to h1? False >>> print("Which one is biggest?", max(houses)) Which one is biggest? h3: 1101 square foot Split >>> print("Which is smallest?", min(houses)) Which is smallest? h2: 846 square foot Ranch >>>
Если вы написали код, который заставляет класс поддерживать все основные операторы сравнения, тогда total_ordering, вероятно, уже не кажется вам такой уж магией: он буквально определяет отображение каждого метода поддержки сравнений на все другие, которые потребуются. Так что если вы определили __lt__() в вашем классе, как показано в решении, то этот метод используется для построения всех других операторов сравнения. Он просто заполняет класс методами:
class House: def __eq__(self, other): . . . def __lt__(self, other): . . . # Методы, созданные @total_ordering __le__ = lambda self, other: self < other or self == other __gt__ = lambda self, other: not (self < other or self == other) __ge__ = lambda self, other: not (self < other) __ne__ = lambda self, other: not self == other
Несложно, конечно, написать такие методы вручную, но @total_ordering делает все наверняка, ни о чем не забывая.
На следующем шаге мы рассмотрим создание закешированных экземпляров.