对于未来的读者,我发布这个答案是因为@wim对这个问题给予了赏金,断言@Marcin的答案function < int
与将评估为的推理是错误的False
,而不是 True
按照类型名称按字典顺序排序时所预期的那样。
以下答案应阐明CPython实现的一些例外情况;但是,它只与 Python 相关,因为这种比较现在会在 Python +2.x
中引发异常。3.x
比较算法
Python的比较算法非常复杂;当使用类型的内置比较函数进行比较时两种类型不兼容时,它在内部默认使用几个不同的函数以尝试找到一致的顺序;这个问题的相关问题是default_3way_compare(PyObject *v, PyObject *w)
。
for 的实现对default_3way_compare
类型的对象名称而不是它们的实际值执行比较(使用字典顺序)(例如,如果类型a
和b
在 中不兼容,它在 C 代码内部a < b
类似地执行)。type(a).__name__ < type(b).__name__
但是,有一些例外情况不遵守此一般规则:
None
:总是被认为小于(即小于)任何其他值(None
当然不包括其他值,因为它们都是相同的实例)。
数字类型(例如int
,float
等):任何返回非零值的类型PyNumber_Check
(也记录在这里)将其类型的名称解析为空字符串""
,而不是它们的实际类型名称(例如“int”、“float”、 ETC)。这需要在任何其他类型(不包括)之前NoneType
对数字类型进行排序。这似乎不适用于该complex
类型。
例如,当使用语句比较数值类型与函数时3 < foo()
,比较内部会解析为 的字符串比较"" < "function"
,即True
,尽管预期的一般情况解析"int" < "function"
实际上是False
因为字典顺序。这种额外的行为是促成上述赏金的原因,因为它违背了预期的类型名称的字典顺序。
有关一些有趣的行为,请参阅以下 REPL 输出:
>>> sorted([3, None, foo, len, list, 3.5, 1.5])
[None, 1.5, 3, 3.5, <built-in function len>, <function foo at 0x7f07578782d0>, <type 'list'>]
更多示例(在 Python 2.7.17 中)
from pprint import pprint
def foo(): return 3
class Bar(float): pass
bar = Bar(1.5)
pprint(map(
lambda x: (x, type(x).__name__),
sorted(
[3, None, foo, len, list, -0.5, 0.5, True, False, bar]
)
))
输出:
[(None, 'NoneType'),
(-0.5, 'float'),
(False, 'bool'),
(0.5, 'float'),
(True, 'bool'),
(1.5, 'Bar'),
(3, 'int'),
(<built-in function len>, 'builtin_function_or_method'),
(<function foo at 0x10c692e50>, 'function'),
(<type 'list'>, 'type')]
额外的见解
Python 的比较算法在Object/object.c
的源代码中实现,并为正在比较的两个对象调用do_cmp(PyObject *v, PyObject *w)
。每个PyObject
实例都有一个对其内置PyTypeObject
类型的引用py_object->ob_type
。PyTypeObject
"instances" 能够指定一个tp_compare
比较函数来评估相同给定的两个对象的排序PyTypeObject
;例如,int
的比较函数在这里注册,在这里实现。但是,此比较系统不支持在各种不兼容类型之间定义附加行为。
Python 通过实现自己的用于不兼容对象类型的比较算法来弥补这一差距,实现于do_cmp(PyObject *v, PyObject *w)
. 有三种不同的尝试来比较类型而不是使用对象的tp_compare
实现:try_rich_to_3way_compare
、try_3way_compare
和 finally default_3way_compare
(我们在这个问题中看到这种有趣行为的实现)。