Rich Comparison Methods

One of the nicest features of the Data Model is the ability to override the behavior of rich comparison operators:

import functools

@functools.total_ordering
class Generic(object):
    def __init__(self, id_):
        self.id = id_

    def __repr__(self):
        return '<Generic id={}>'.format(self.id)

    def __eq__(self, other):
        if isinstance(other, self.__class__):
            return self.id == other.id
        return NotImplemented

    def __ne__(self, other):
        if isinstance(other, self.__class__):
            return self.id != other.id
        return NotImplemented

    def __lt__(self, other):
        if isinstance(other, self.__class__):
            return self.id < other.id
        return NotImplemented

Now we can conveniently compare two Generic objects to one another without having to access the idattribute over and over:

>>> Generic(1) == Generic(1)
True
>>> Generic(1) < Generic(2)
True
>>> # etc.

We can even compare Generic objects to objects of other types, and because we’re using NotImplemented, we’ll get a reasonable result:

>>> Generic(1) == 1
False
>>> Generic(1) < 2
False
>>> # etc.

Which Method is Called?

In the simple cases above, it’s trivial to determine which comparison methods Python prefers. But to get a more complete picture of what’s going on under the hood, let’s decorate our overridden class to print to stdoutanytime a rich comparison method is called:

def chirp_richcomps(cls):
    '''Decorator which prints to stdout everytime a rich-comparison method is
       called'''
    def chirp_factory(method, attr):
        def inner(self, other):
            print '{}: Called {}'.format(self, attr)
            return method(self, other)
        return inner

    for attr in ('eq', 'ne', 'lt', 'le', 'gt', 'ge'):
        attr = '__{}__'.format(attr)
        method = getattr(cls, attr)
        if method is not None:
            setattr(cls, attr, chirp_factory(method, attr))

    return cls

Generic = chirp_richcomps(Generic)
Case #1: Only One Side Has a Comparison Override
>>> Generic(1) < 1
<Generic id=1>: Called __lt__
False
>>> 1 > Generic(1)
<Generic id=1>: Called __lt__
False

In the case where only one side of the comparison supports an override, the object with a comparison override is preferred regardless of side. When the object with the override is on the right side, and >>=< or <= are used, python will use the reflection method of the invoked operator. In the above example this means 1 < Generic(1) actually invokes Generic(1).__lt__(1). Similarly < would invoke __gt__<= would invoke__ge__ and >= would invoke __le__.

Case #2: Both Sides Have a Comparison Override
>>> Generic(1) < Generic(2)
<Generic id=1>: Called __lt__
>>> # set overrides comparison operators
>>> Generic(1) == set()
<Generic id=1>: Called __eq__
>>> # let's take a closer look at that
>>> @chirp_richcomps
... class ChirpSet(set)
...     pass
...
>>> Generic(1) == ChirpSet()
<Generic id=1>: Called __eq__
ChirpSet([]): Called __eq__
>>> ChirpSet() == Generic(1)
ChirpSet([]): Called __eq__

In the naive case where both sides have a comparison override, Python prefers the left side. The comparison against ChirpSet above demonstrates what happens if the left-side comparison returns the NotImplementedconstant, which will cause Python to fall-back to use the comparison operator from the right side if present. The final example from above shows that unlike Genericset does not return NotImplemented when compared against a non-set, preferring to simply return False instead (which we see becauseGeneric(1).__eq__(ChirpSet()) is not attempted.

Case #3: Both Sides Have a Comparison Override, But One Side Inherits The Other
>>> class Specific(Generic):
...     pass
>>> Generic(1) == Specific(1)
<Specific id=1>: Called __eq__
<Generic id=1>: Called __eq__
True
>>> class ReallySpecific(Specific):
...     pass
>>> ReallySpecific(1) == Generic(1)
<ReallySpecific id=1>: Called __eq__
<Generic id=1>: Called __eq__
True

In this final case we’ve setup the classes Specific and ReallySpecific which directly and indirectly inheritGeneric to demonstrate what happens when one side of the comparison includes the type of the other side in its mro. In this case, the more specific type is preferred, regardless of which side it happens to be on. Note that this doesn’t happen in the case of shared inheritance:

>>> class OtherSpecific(Generic):
...     pass
>>> Specific(1) == OtherSpecific(1)
<Specific id=1>: Called __eq__
<OtherSpecific id=1>: Called __eq__
<OtherSpecific id=1>: Called __eq__
<Specific id=1>: Called __eq__
False
>>> OtherSpecific(1) == ReallySpecific(1)
<OtherSpecific id=1>: Called __eq__
<ReallySpecific id=1>: Called __eq__
<ReallySpecific id=1>: Called __eq__
<OtherSpecific id=1>: Called __eq__
False

As to why each method is called twice in this case … that’s another topic for another day.

From:

Rich Comparison Methods


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值