- 6.10. 比较运算
- 6.10.1. 值比较
- 6.10.2. 成员检测运算
- 6.10.3. 标识号比较
6.10. 比较运算
与 C 不同,Python 中所有比较运算的优先级相同,低于任何算术、移位或位运算。 另一个与 C 不同之处在于 a < b < c
这样的表达式会按传统算术法则来解读:
- comparison ::=
or_expr
(comp_operator
or_expr
)*- comp_operator ::= "<" | ">" | "==" | ">=" | "<=" | "!="
- | "is" ["not"] | ["not"] "in"
比较运算将输出布尔值: True
或 False
。
比较运算可以任意串连,例如 x < y <= z
等价于 x < y and y <= z
,除了 y
只被求值一次(但在两种写法下当 x < y
值为假时 z
都不会被求值)。
正式的说法是这样:如果 a, b, c, …, y, z 为表达式而 op1, op2, …, opN 为比较运算符,则 a op1 b op2 c … y opN z
就等价于 a op1 b and b op2 c and … y opN z
,后者的不同之处只是每个表达式最多只被求值一次。
请注意 a op1 b op2 c
不意味着在 a 和 c 之间进行任何比较,因此,如 x < y > z
这样的写法是完全合法的(虽然也许不太好看)。
6.10.1. 值比较
运算符 <
, >
, ==
, >=
, <=
和 !=
将比较两个对象的值。 两个对象不要求为相同类型。
对象、值与类型 一章已说明对象都有相应的值(还有类型和标识号)。 对象值在 Python 中是一个相当抽象的概念:例如,对象值并没有一个规范的访问方法。 而且,对象值并不要求具有特定的构建方式,例如由其全部数据属性组成等。 比较运算符实现了一个特定的对象值概念。 人们可以认为这是通过实现对象比较间接地定义了对象值。
由于所有类型都是 object
的(直接或间接)子类型,它们都从 object
继承了默认的比较行为。 类型可以通过实现 丰富比较方法 例如 lt()
来定义自己的比较行为,详情参见 基本定制。
默认的一致性比较 (==
和 !=
) 是基于对象的标识号。 因此,具有相同标识号的实例一致性比较结果为相等,具有不同标识号的实例一致性比较结果为不等。 规定这种默认行为的动机是希望所有对象都应该是自反射的 (即 x is y
就意味着 x == y
)。
次序比较 (<
, >
, <=
和 >=
) 默认没有提供;如果尝试比较会引发 TypeError
。 规定这种默认行为的原因是缺少与一致性比较类似的固定值。
按照默认的一致性比较行为,具有不同标识号的实例总是不相等,这可能不适合某些对象值需要有合理定义并有基于值的一致性的类型。 这样的类型需要定制自己的比较行为,实际上,许多内置类型都是这样做的。
以下列表描述了最主要内置类型的比较行为。
- 内置数值类型 (数字类型 —- int, float, complex) 以及标准库类型
fractions.Fraction
和decimal.Decimal
可进行类型内部和跨类型的比较,例外限制是复数不支持次序比较。 在类型相关的限制以内,它们会按数学(算法)规则正确进行比较且不会有精度损失。
非数字值 float('NaN')
和 decimal.Decimal('NaN')
属于特例。 任何数字与非数字值的比较均返回假值。 还有一个反直觉的结果是非数字值不等于其自身。 例如,如果 x = float('NaN')
则 3 < x
, x < 3
, x == x
, x != x
均为假值。 此行为是符合 IEEE 754 标准的。
None
和NotImplemented
都是单例对象。 PEP 8 建议单例对象的比较应当总是通过is
或is not
而不是等于运算符来进行。二进制码序列 (
bytes
或bytearray
的实例) 可进行类型内部和跨类型的比较。 它们使用其元素的数字值按字典顺序进行比较。字符串 (
str
的实例) 使用其字符的 Unicode 码位数字值 (内置函数ord()
的结果) 按字典顺序进行比较。 3
字符串和二进制码序列不能直接比较。
- 序列 (
tuple
,list
或range
的实例) 只可进行类型内部的比较,range 还有一个限制是不支持次序比较。 以上对象的跨类型一致性比较结果将是不相等,跨类型次序比较将引发TypeError
。
序列比较是按字典序对相应元素进行逐个比较。 内置容器通常设定同一对象与其自身是相等的。 这使得它们能跳过同一对象的相等性检测以提升运行效率并保持它们的内部不变性。
内置多项集间的字典序比较规则如下:
两个多项集若要相等,它们必须为相同类型、相同长度,并且每对相应的元素都必须相等(例如,
[1,2] == (1,2)
为假值,因为类型不同)。对于支持次序比较的多项集,排序与其第一个不相等元素的排序相同(例如
[1,2,x] <= [1,2,y]
的值与x <= y
相同)。 如果对应元素不存在,较短的多项集排序在前(例如[1,2] < [1,2,3]
为真值)。
- 两个映射 (
dict
的实例) 若要相等,必须当且仅当它们具有相同的 (键, 值) 对。 键和值的一致性比较强制规定自反射性。
次序比较 (<
, >
, <=
和 >=
) 将引发 TypeError
。
- 集合 (
set
或frozenset
的实例) 可进行类型内部和跨类型的比较。
它们将比较运算符定义为子集和超集检测。 这类关系没有定义完全排序(例如 {1,2}
和 {2,3}
两个集合不相等,即不为彼此的子集,也不为彼此的超集。 相应地,集合不适宜作为依赖于完全排序的函数的参数(例如如果给出一个集合列表作为 min()
, max()
和 sorted()
的输入将产生未定义的结果)。
集合的比较强制规定其元素的自反射性。
- 大多数其他内置类型没有实现比较方法,因此它们会继承默认的比较行为。
在可能的情况下,用户定义类在定制其比较行为时应当遵循一些一致性规则:
- 相等比较应该是自反射的。 换句话说,相同的对象比较时应该相等:
x is y
意味着x == y
- 比较应该是对称的。 换句话说,下列表达式应该有相同的结果:
x == y
和y == x
x != y
和y != x
x < y
和y > x
x <= y
和y >= x
- 比较应该是可传递的。 下列(简要的)例子显示了这一点:
x > y and y > z
意味着x > z
x < y and y <= z
意味着x < z
- 反向比较应该导致布尔值取反。 换句话说,下列表达式应该有相同的结果:
x == y
和not x != y
x < y
和not x >= y
(对于完全排序)
x > y
和not x <= y
(对于完全排序)
最后两个表达式适用于完全排序的多项集(即序列而非集合或映射)。 另请参阅 total_ordering()
装饰器。
hash()
的结果应该与是否相等一致。 相等的对象应该或者具有相同的哈希值,或者标记为不可哈希。
Python 并不强制要求这些一致性规则。 实际上,非数字值就是一个不遵循这些规则的例子。
6.10.2. 成员检测运算
运算符 in
和 not in
用于成员检测。 如果 x 是 s 的成员则 x in s
求值为 True
,否则为 False
。 x not in s
返回 x in s
取反后的值。 所有内置序列和集合类型以及字典都支持此运算,对于字典来说 in
检测其是否有给定的键。 对于 list, tuple, set, frozenset, dict 或 collections.deque 这样的容器类型,表达式 x in y
等价于 any(x is e or x == e for e in y)
。
对于字符串和字节串类型来说,当且仅当 x 是 y 的子串时 x in y
为 True
。 一个等价的检测是 y.find(x) != -1
。 空字符串总是被视为任何其他字符串的子串,因此 "" in "abc"
将返回 True
。
对于定义了 contains()
方法的用户自定义类来说,如果 y.contains(x)
返回真值则 x in y
返回 True
,否则返回 False
。
对于未定义 contains()
但定义了 iter()
的用户自定义类来说,如果在对 y
进行迭代时产生了值 z
使得表达式 x is z or x == z
为真,则 x in y
为 True
。 如果在迭代期间引发了异常,则等同于 in
引发了该异常。
最后将会尝试旧式的迭代协议:如果一个类定义了 getitem()
,则当且仅当存在非负整数索引号 i 使得 x is y[i] or x == y[i]
并且没有更小的索引号引发 IndexError
异常时 x in y
为 True
。 (如果引发了任何其他异常,则等同于 in
引发了该异常)。
运算符 not in
被定义为具有与 in
相反的逻辑值。
6.10.3. 标识号比较
运算符 is
和 is not
用于检测对象的标识号:当且仅当 x 和 y 是同一对象时 x is y
为真。 一个对象的标识号可使用 id()
函数来确定。 x is not y
会产生相反的逻辑值。 4