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

Posted

技术标签:

【中文标题】iter() 不适用于 datetime.now()【英文标题】:iter() not working with datetime.now() 【发布时间】:2017-11-01 04:27:42 【问题描述】:

Python 3.6.1 中的一个简单的 sn-p:

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

返回:

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

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

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

【问题讨论】:

有趣,我希望这能奏效。它也适用于 3.4 和 3.5。 datetime.datetime.now 替换为lambda: datetime.datetime.now()partial(datetime.datetime.now) 时有效。 我想你应该在他们的bug tracker 报告这个。 【参考方案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 的一部分。

【讨论】:

+1,但作为一个兴趣点,您为什么更喜欢 partial 而不是 lambda 之类的东西(就像您的评论中一样)? @erip:因为它更快。无需创建Python框架,C实现可以直接调用datetime.datetime.nowC函数。【参考方案2】:

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

在这种情况下,违规者是在 callable_iterator.__next__(您的对象是 callable_iterator)方法的 C 等效项中将 PyObject_Call 替换为 _PyObject_CallNoArg

PyObject_Call 确实返回一个新的datetime.datetime 实例,而_PyObject_CallNoArg 返回NULL(这大致相当于 Python 中的异常)。

通过 CPython 源代码挖掘一下:

_PyObject_CallNoArg 只是 _PyObject_FastCall 的宏,而后者又是 _PyObject_FastCallDict 的宏。

This _PyObject_FastCallDict function 检查函数的类型(C-function 或 Python 函数或其他)并在这种情况下委托给 _PyCFunction_FastCallDict,因为 datetime.now 是 C 函数。

由于datetime.datetime.nowMETH_FASTCALL 标志,它会在第四个case 结束,但_PyStack_UnpackDict 返回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)

【讨论】:

很好的答案,增加了对罪魁祸首的洞察力。我将在 Martijn 的回答中保留已接受的标记,因为他已经提交了 Python 项目的问题。 这是 FASTCALL 优化中已确认的错误,Victor Stinner 在github.com/python/cpython/pull/1886 有一个初步补丁

以上是关于iter() 不适用于 datetime.now()的主要内容,如果未能解决你的问题,请参考以下文章

使用 DateTime.Now.Ticks 生成唯一的数字 ID

为啥 fill_n() 不适用于 vector.reserve()?

git hook之commit-msg用于检测提交时间是否正确

git hook之commit-msg用于检测提交时间是否正确

datetime 模块

C# 怎样把 DateTime.Now.Ticks转换为常规日期的形式