Python漫谈-比较运算符和类的神奇方法

昨天遇到一个Python问题,今天好奇试了一下

>>> a = {1:23,'ab':56}

>>> b = {2:22,'ab':57}

>>> a > b

False

>>> a < b

True

>>> b = {1:22,'ab':57}

>>> a > b

True

>>> a < b

False

好吧。。。真无聊,什么都要比一比,提供这种不科学不明了的默认实现有意思么

既来之则安之,八一八好了,Python为dictionary对象提供了一套默认实现

x<y calls x.__lt__(y)x<=y callsx.__le__(y)x==y calls x.__eq__(y)x!=y and x<>y call x.__ne__(y)x>y calls x.__gt__(y), and x>=y calls x.__ge__(y)

而且

The truth of x==y does not imply that x!=y is false. Accordingly, when defining __eq__(), one should also define __ne__() so that the operators will behave as expected.

自己写个类的话,真麻烦。。。

不过还好

To automatically generate ordering operations from a single root operation, see functools.total_ordering().

functools.total_ordering

(

cls

)

Given a class defining one or more rich comparison ordering methods, this class decorator supplies the rest. This simplifies the effort involved in specifying all of the possible rich comparison operations:

The class must define one of __lt__()__le__()__gt__(), or __ge__(). In addition, the class should supply an __eq__() method.

For example:
@total_ordering
class Student:
    def __eq__(self, other):
        return ((self.lastname.lower(), self.firstname.lower()) ==
                (other.lastname.lower(), other.firstname.lower()))
    def __lt__(self, other):
        return ((self.lastname.lower(), self.firstname.lower()) <
                (other.lastname.lower(), other.firstname.lower()))
New in version 2.7.

在谷歌的过程中在译言发现了一篇有趣的文章,其他部分也值得一读

Python的神奇方法指南

3.1 神奇方法——比较
Python有一整套神奇方法被设计用来通过操作符实现对象间直观的比较,而非别扭的方法调用。它们同样提供了一套覆盖Python对象比较的默认行为(通过引用)。以下是这些方法的列表以及做法:


__cmp__(self, other)
__cmp__是神奇方法中最基础的一个。实际上它实现所有比较操作符行为(<,==,!=,等),但它有可能不按你想要的方法工作(例如,一个实例是否等于另一个这取决于比较的准则,以及一个实例是否大于其他的这也取决于其他的准则)。如果self < other,那__cmp__应当返回一个负整数;如果self == other,则返回0;如果self > other,则返回正整数。它通常是最好的定义,而不需要你一次就全定义好它们,但当你需要用类似的准则进行所有的比较时,__cmp__会是一个很好的方式,帮你节省重复性和提高明确度。


__eq__(self, other)
定义了相等操作符,==的行为。
__ne__(self, other)
定义了不相等操作符,!=的行为。
__lt__(self, other)
定义了小于操作符,<的行为。
__gt__(self, other)
定义了大于操作符,>的行为。
__le__(self, other)
定义了小于等于操作符,<=的行为。
__ge__(self, other)
定义了大于等于操作符,>=的行为。


举一个例子,设想对单词进行类定义。我们可能希望能够按内部对string的默认比较行为,即字典序(通过字母)来比较单词,也希望能够基于某些其他的准则,像是长度或音节数。在本例中,我们通过单词长度排序,以下给出实现:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Word(str):
    '''单词类,比较定义是基于单词长度的'''
                                            
    def __new__(cls, word):
        # 注意,我们使用了__new__,这是因为str是一个不可变类型,
        # 所以我们必须更早地初始化它(在创建时)
        if ' ' in word:
            print "单词内含有空格,截断到第一部分"
            word = word[:word.index(' ')] # 在出现第一个空格之前全是字符了现在
        return str.__new__(cls, word)
                                                
    def __gt__(self, other):
        return len(self) > len(other)
    def __lt__(self, other):
        return len(self) < len(other)
    def __ge__(self, other):
        return len(self) >= len(other)
    def __le__(self, other):
        return len(self) <= len(other)

 

 

现在,我们可以创建2个单词(通过Word('foo')和Word('bar'))并基于它们的长度进行比较了。注意,我们没有定义__eq__ 和 __ne__。这是因为这可能导致某些怪异的行为(特别是当比较Word('foo') == Word('bar')将会得到True的结果)。基于单词长度的相等比较会令人摸不清头脑,因此我们就沿用了str本身的相等比较的实现。


现在可能是一个好时机来提醒你一下,你不必重载每一个比较相关的神奇方法来获得各种比较。标准库已经友好地为我们在模板functools中提供了一个装饰(decorator)类,定义了所有比较方法。你可以只重载__eq__和一个其他的方法(比如__gt__,__lt__,等)。这个特性只在Python2.7(后?)适用,但当你有机会的话应该尝试一下,它会为你省下大量的时间和麻烦。你可以通过在你自己的重载方法在加上@total_ordering来使用。

3.1 神奇方法——比较
Python有一整套神奇方法被设计用来通过操作符实现对象间直观的比较,而非别扭的方法调用。它们同样提供了一套覆盖Python对象比较的默认行为(通过引用)。以下是这些方法的列表以及做法:


__cmp__(self, other)
__cmp__是神奇方法中最基础的一个。实际上它实现所有比较操作符行为(<,==,!=,等),但它有可能不按你想要的方法工作(例如,一个实例是否等于另一个这取决于比较的准则,以及一个实例是否大于其他的这也取决于其他的准则)。如果self < other,那__cmp__应当返回一个负整数;如果self == other,则返回0;如果self > other,则返回正整数。它通常是最好的定义,而不需要你一次就全定义好它们,但当你需要用类似的准则进行所有的比较时,__cmp__会是一个很好的方式,帮你节省重复性和提高明确度。


__eq__(self, other)
定义了相等操作符,==的行为。
__ne__(self, other)
定义了不相等操作符,!=的行为。
__lt__(self, other)
定义了小于操作符,<的行为。
__gt__(self, other)
定义了大于操作符,>的行为。
__le__(self, other)
定义了小于等于操作符,<=的行为。
__ge__(self, other)
定义了大于等于操作符,>=的行为。


举一个例子,设想对单词进行类定义。我们可能希望能够按内部对string的默认比较行为,即字典序(通过字母)来比较单词,也希望能够基于某些其他的准则,像是长度或音节数。在本例中,我们通过单词长度排序,以下给出实现:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Word(str):
    '''单词类,比较定义是基于单词长度的'''
                                            
    def __new__(cls, word):
        # 注意,我们使用了__new__,这是因为str是一个不可变类型,
        # 所以我们必须更早地初始化它(在创建时)
        if ' ' in word:
            print "单词内含有空格,截断到第一部分"
            word = word[:word.index(' ')] # 在出现第一个空格之前全是字符了现在
        return str.__new__(cls, word)
                                                
    def __gt__(self, other):
        return len(self) > len(other)
    def __lt__(self, other):
        return len(self) < len(other)
    def __ge__(self, other):
        return len(self) >= len(other)
    def __le__(self, other):
        return len(self) <= len(other)

 

 

现在,我们可以创建2个单词(通过Word('foo')和Word('bar'))并基于它们的长度进行比较了。注意,我们没有定义__eq__ 和 __ne__。这是因为这可能导致某些怪异的行为(特别是当比较Word('foo') == Word('bar')将会得到True的结果)。基于单词长度的相等比较会令人摸不清头脑,因此我们就沿用了str本身的相等比较的实现。


现在可能是一个好时机来提醒你一下,你不必重载每一个比较相关的神奇方法来获得各种比较。标准库已经友好地为我们在模板functools中提供了一个装饰(decorator)类,定义了所有比较方法。你可以只重载__eq__和一个其他的方法(比如__gt__,__lt__,等)。这个特性只在Python2.7(后?)适用,但当你有机会的话应该尝试一下,它会为你省下大量的时间和麻烦。你可以通过在你自己的重载方法在加上@total_ordering来使用。

posted on 2014-01-11 14:01  小唯THU  阅读(1918)  评论(0编辑  收藏  举报

导航