数据比较

一般来说,数据对象需要相互比较。

两个变量a和b所引用的对象,可以进行如下各种比较操作:

  • a < b
  • a > b
  • a == b
  • a >= b
  • a <= b
在Python中,如果自定义对象类型实现上述各项比较功能,就必须定义相应的方法,比如要实现“==”和“<”比较,就要分别实现“__eq__”和“__lt__”两个特殊方法(详见《跟老齐学Python:轻松入门》中的说明)。下面是一个简单的例子:
 
class Number:
    def __init__( self, val = 0):
        self.val = val
 
    def __eq__(self, other):
        return self.val == other.val
 
    def __lt__(self, other):
        return self.val < other.val

这是一种通常的自定义对象类型,并该类型对象能够实现比较“==”和“<”的比较运算。

如果使用@dataclass,则让代码简单了许多。

@dataclass(order = True)

class Number:
    val: int = 0

就这么多代码,惊讶了吧。

不需要定义__eq__和__lt__方法。只需要在@dataclass装饰器的参数中设置order = True即可,就自动在所定义的类中实现了这两个特殊方法。

那么,怎么做到的呢?

当你使用@dataclass时,它会向类定义中添加函数__eq__和__lt__。这是我们已经知道的。那么,这些函数是如何知道检查是否相等和进行比较的呢?

一个由dataclass生成的__eq__函数将把属性元组与相同类的另一个实例的属性的元组进行比较。在我们的例子中,由__eq__方法自动生成的对象将等同于:

def __eq__(self, other):
    return (self.val,) == (other.val,)

让我们来看一个更详细的例子:

写一个数据类Person来保存姓名和年龄。

@dataclass(order = True)
class Person:
    name: str
    age:int = 0

这个自动生成的__eq__方法将等价于:

def __eq__(self, other):
    return (self.name, self.age) == ( other.name, other.age)

注意属性的顺序。它们总是按照你在数据类中定义的顺序生成的。

同理,等效的__le__函数将类似于这样:

def __le__(self, other):
    return (self.name, self.age) <= (other.name, other.age)

因为数据对象中已经默认实现了各种比较功能,因此就可以实现排序。

>>> import random
>>> a = [Number(random.randint(1,10)) for _ in range(10)] #generate list of random numbers
>>> a
>>> [Number(val=2), Number(val=7), Number(val=6), Number(val=5), Number(val=10), Number(val=9), Number(val=1), Number(val=10), Number(val=1), Number(val=7)]
>>> sorted_a = sorted(a) #Sort Numbers in ascending order
>>> [Number(val=1), Number(val=1), Number(val=2), Number(val=5), Number(val=6), Number(val=7), Number(val=7), Number(val=9), Number(val=10), Number(val=10)]
>>> reverse_sorted_a = sorted(a, reverse = True) #Sort Numbers in descending order 
>>> reverse_sorted_a
>>> [Number(val=10), Number(val=10), Number(val=9), Number(val=7), Number(val=7), Number(val=6), Number(val=5), Number(val=2), Number(val=1), Number(val=1)]