8.24 让类支�比较�作¶

问题¶

你想让�个类的实例支�标准的比较�算(比如>=,!=,<=,<等),但是��想去实现那一大丢的特殊方法。

解决方案¶

Python类对�个比较�作都需�实现一个特殊方法�支�。 例如为了支�>=�作符,你需�定义一个 __ge__() 方法。 尽管定义一个方法没什么问题,但如果�你实现所有�能的比较方法那就有点烦人了。

装饰器 functools.total_ordering 就是用�简化这个处�的。 使用它�装饰一个类,你�需定义一个 __eq__() 方法, 外加其他方法(__lt__, __le__, __gt__, or __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类定义了两个方法:__eq__() 和 __lt__() ,它就能支�所有的比较�作:

# Build a few houses, and add rooms to them
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) # prints True
print('Is h2 smaller than h3?', h2 < h3) # prints True
print('Is h2 greater than or equal to h1?', h2 >= h1) # Prints False
print('Which one is biggest?', max(houses)) # Prints 'h3: 1101-square-foot Split'
print('Which is smallest?', min(houses)) # Prints 'h2: 846-square-foot Ranch'

讨论¶

其实 total_ordering 装饰器也没那么神秘。 它就是定义了一个从�个比较支�方法到所有需�定义的其他方法的一个映射而已。 比如你定义了 __le__() 方法,那么它就被用�构建所有其他的需�定义的那些特殊方法。 实际上就是在类里��下�这样定义了一些特殊方法:

class House:
    def __eq__(self, other):
        pass
    def __lt__(self, other):
        pass
    # Methods created by @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 �以简化代�,何�而�为呢。