iter() 不适用于 datetime.now()

iter() not working with datetime.now()

Python 3.6.1 中的一个简单片段:

import datetime
j = iter(datetime.datetime.now, None)
next(j)

returns:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

而不是打印出每个 next() 的经典 now() 行为。

我在 Python 3.3 中看到过类似的代码,我是不是遗漏了什么或者在 3.6.1 版中有什么变化?

这绝对是 Python 3.6.0b1 中引入的错误。 iter() 实现最近切换到使用 _PyObject_FastCall()(优化,参见 issue 27128),一定是这个调用打破了这个。

Argument Clinic 解析支持的其他 C classmethod 方法也会出现同样的问题:

>>> from asyncio import Task
>>> Task.all_tasks()
set()
>>> next(iter(Task.all_tasks, None))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

如果您需要解决方法,请将可调用对象包装在 functools.partial() 对象中:

from functools import partial

j = iter(partial(datetime.datetime.now), None)

我用 Python 项目提交了 issue 30524 -- iter(classmethod, sentinel) broken for Argument Clinic class methods?。对此的修复已经着陆并且是 3.6.2rc1 的一部分。

我假设您使用的是 CPython 而不是另一个 Python 实现。我可以用 CPython 3.6.1 重现这个问题(我没有 PyPy、Jython、IronPython,...所以我无法检查这些)。

这种情况下的违规者是 callable_iterator.__next__(您的对象是 callable_iterator)方法的 C 等价物中 PyObject_Call with _PyObject_CallNoArg 的替换。

PyObject_Call 做 return 一个新的 datetime.datetime 实例,而 _PyObject_CallNoArg returns NULL (这大致相当于一个异常在 Python).

深入了解 CPython 源代码:

_PyObject_CallNoArg 只是 _PyObject_FastCall which in turn is a macro for _PyObject_FastCallDict 的一个宏。

This _PyObject_FastCallDict function checks the type of the function (C-function or Python function or something else) and delegates to _PyCFunction_FastCallDict 在这种情况下,因为 datetime.now 是一个 C 函数。

因为 datetime.datetime.nowMETH_FASTCALL 标志,所以它在第四个 case 结束,但是 _PyStack_UnpackDict returns NULL 和甚至从未调用函数。

我会就此打住,让 Python 开发人员弄清楚那里出了什么问题。 @Martijn Pieters 已经提交了错误报告,他们会修复它(我只是希望他们尽快修复它)。

所以这是他们在 3.6 中引入的错误,在修复之前,您需要确保该方法不是带有 METH_FASTCALL 标志的 CFunction。作为解决方法,您可以包装它。除了@Martijn Pieters 提到的可能性之外,还有一个简单的:

def now():
    return datetime.datetime.now()

j = iter(now, None)
next(j)  # datetime.datetime(2017, 5, 31, 14, 23, 1, 95999)