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.now
C函数。【参考方案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.now
有METH_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用于检测提交时间是否正确