比较运算符 < 和 > 如何将函数用作操作数?

Posted

技术标签:

【中文标题】比较运算符 < 和 > 如何将函数用作操作数?【英文标题】:How do comparison operators < and > work with a function as an operand? 【发布时间】:2013-08-25 15:01:32 【问题描述】:

遇到了这个问题(在 Python 2.7.5 中),有点错字:

def foo(): return 3
if foo > 8:
    launch_the_nukes()

该死,我不小心把月球炸了。

我的理解是E &gt; F 等价于(E).__gt__(F),而对于表现良好的类(例如内置),则等价于(F).__lt__(E)

如果没有 __lt____gt__ 运算符,那么我认为 Python 使用 __cmp__

但是,这些方法都不适用于 function 对象,而 &lt;&gt; 运算符 do 工作。导致这种情况发生的幕后原因是什么?

>>> foo > 9e9
True
>>> (foo).__gt__(9e9)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'function' object has no attribute '__gt__'
>>> (9e9).__lt__(foo)
NotImplemented

【问题讨论】:

请注意,在 Python 3.0+ 中,这个小错字会给你一个明显的TypeError: unorderable types: function() &gt; int()。换句话说,这是一个众所周知的问题已经解决了,如果你使用旧版本的语言,你只需要担心它。 @digi_abhshk:就像 OP 在他的代码中那样做。保证每次都返回一致的东西,但不能保证 what 它返回。 (在 CPython 2.2-2.7 中,它将有效地按名称比较对象的类型。) @Cuadue:是的,但这不是相反的。抛开抽象,考虑一个简单的例子:3 &gt; 3 不是真的,尽管3 &lt;= 3 是,对吧?如果你想在这里使用逆,你会说E &lt; F iff not E &gt;= F,而不是E &lt; F iff F &gt;= E @Cuadue:使用许多(但不是全部)运算符特殊方法,返回 NotImplemented(或者,在 C 运算符槽中,返回一个特殊值——IIRC 0、-1、-2 或 2都用在不同的地方)意味着“转到下一个后备来实现这个操作符”。这是为__lt__ here 记录的。 @PaulMcGuire 那是错字 【参考方案1】:

但是,这些方法都不适用于函数对象,而 运算符确实有效。导致这种情况发生的幕后原因是什么?

默认情况下,2.x 系列中的 CPython 基于类型名称进行比较。 (这里是documented as an implementation detail,虽然有一些有趣的例外只能在in the source 中找到。)在3.x 系列中这将导致一个例外。

Python 规范对 2.x 中的行为设置了一些特定的限制;按类型名称进行比较并不是唯一允许的行为,其他实现可能会做其他事情。这不是可以依赖的东西。

【讨论】:

很奇怪。看起来&lt;type 'function'&gt; 实际上小于 &lt;type 'int'&gt;。所以看起来type(foo) &gt; type(8) 评估为False @Cuadue:啊,这是一个不同的微妙之处,没有记录,我希望不会出现。正在比较的名称实际上并不是您从其__name__ 属性中看到的名称,这可能就是这里发生的情况。老实说,如果不查找来源,我无法保证这一点。这里可能很有趣…… 我认为是default_3way_compare 实现了后备代码。这有点令人惊讶:不通过PyNumber_Check 的对象使用其类型对象的tp_name 槽……但do 的对象都被视为具有名称"" . (如果 两边 都是数字,它们会通过类型对象的 id 进行比较。这也发生在具有相同名称的两个不同类型中。) 我编辑了答案以添加到文档的链接(它给出了“按类型名称”规则)和源代码(这表明它不是绝对遵循的);希望任何对完整细节感兴趣的人都会阅读 cmets。可能值得将整个比较过程(由 CPython 2.7 实现)从特殊属性查找到后备中的同名类型后备作为伪代码写下来,以供那些不想追踪源代码的人使用……但我我懒得这么做…… 这里还有另一个转折点。它经历了您期望从文档中获得的相同后备,就像两个用户定义的类一样(请参阅 here 以获取一些显示顺序的代码),除了 int.__cmp__ 存在并引发 TypeError ——将作为用户定义(新样式)类的异常传播,但对于内置类,它仍然像在 Python 1.5 中一样工作,这意味着它会回退到默认比较代码。【参考方案2】:

对于未来的读者,我发布此答案是因为 @wim 已在此问题上悬赏,断言 @Marcin's answer 与 function &lt; int 将评估为 False 的推理是错误的,不是 em> True 如果按类型名称按字典顺序排序,则可以预期。

以下答案应阐明CPython 实现的一些例外情况;但是,它只与 Python 2.x 相关,因为这种比较现在会在 Python 3.x+ 中引发异常。

比较算法

Python 的比较算法非常复杂;当使用该类型的内置比较函数进行比较时两种类型不兼容时,它在内部默认使用几个不同的函数以尝试找到一致的顺序;这个问题的相关问题是default_3way_compare(PyObject *v, PyObject *w)

default_3way_compare 的实现对类型的对象名称而不是它们的实际值执行比较(使用字典顺序)(例如,如果类型 aba &lt; b 中不兼容,它类似地执行 @ 987654348@ 内部在 C 代码中)。

但是,有一些例外情况不遵守这个一般规则:

None总是被认为小于(即小于)任何其他值(当然不包括其他None,如they are are all the same instance)。

数字类型(例如intfloat 等):任何从PyNumber_Check(也记录在here)返回非零值的类型都将其类型名称解析为空字符串@987654354 @ 而不是它们的实际类型名称(例如“int”、“float”等)。这要求数字类型在任何其他类型(不包括NoneType)之前排序。这似乎不适用于complex 类型。

例如,当使用语句 3 &lt; foo() 将数值类型与函数进行比较时,比较在内部解析为字符串比较 "" &lt; "function",即 True,尽管预期的一般情况解析为 @987654360 @ 实际上是 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-&gt;ob_type 的引用。 PyTypeObject "instances" 能够指定一个 tp_compare 比较函数,该函数评估相同给定 PyTypeObject 的两个对象的排序;例如int的比较函数注册here并实现here。然而,这个比较系统支持在各种不兼容的类型之间定义额外的行为。

Python 通过实现自己的不兼容对象类型比较算法来弥补这一差距,在do_cmp(PyObject *v, PyObject *w) 上实现。有三种不同的尝试来比较类型而不是使用对象的 tp_compare 实现:try_rich_to_3way_comparetry_3way_compare,最后是 default_3way_compare(我们在这个问题中看到这种有趣行为的实现)。

【讨论】:

@wim 你应该给这个答案赏金。仅供参考,关于异常的信息在我的回答中简要处理,并附有源链接。 这种设计的动机是在对异构列表进行排序时将数字组合在一起并排序。后来complex type出现了一个例外,初衷已经没有说服力了。历史背景详情here.【参考方案3】:

注意:这只适用于 Python 2.x。在 Python 3.x 中,它给出:

TypeError: 'function' 和 'int' 的实例之间不支持'(comparison operator)'。

在 Python 2.x 中,函数不仅仅是无限大。例如,在 Python 2.7.16 (repl.it) 中,输入:

> def func():
...    return 0
...
> print(func)
<function func at 0xLOCATION>
> int(func)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: int() argument must be a string or a number, not 'function'
> id(func)
IDNUMBER
> x=id(func)
> func<x
False
> func==x
False
> inf=float("inf")
> func<inf
False

这表明 'func' 在 Python 2.x 中大于正无穷大。在Python 3.8.2 (repl.it) 试试这个:

> def func():
...    return 0
...
> func<10
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'function' and 'int'

这表明仅在 Python 2.x 中支持将函数作为操作数进行比较,并且在 Python 2.x 中比较函数时,它大于 Python 的无穷大。

【讨论】:

以上是关于比较运算符 < 和 > 如何将函数用作操作数?的主要内容,如果未能解决你的问题,请参考以下文章

将一个函数模板的模板参数映射到另一个函数模板的参数(c ++)

Hive的运算和函数大全

如何在 BigDecimal 上使用 >、=、< 等比较运算符

我可以将 C++17 无捕获 lambda constexpr 转换运算符的结果用作函数指针模板非类型参数吗?

结构体排序

如何将 Bootstrap 下拉菜单用作 HTML 表单 <select>