iter()不使用datetime.now()

Vid*_*dak 44 python datetime iterable python-3.6

Python 3.6.1中的一个简单片段:

import datetime
j = iter(datetime.datetime.now, None)
next(j)
Run Code Online (Sandbox Code Playgroud)

收益:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
Run Code Online (Sandbox Code Playgroud)

而不是打印now()每个经典行为next().

我已经看到类似的代码在Python 3.3中工作,我在版本3.6.1中遗漏了什么或者有什么变化?

Mar*_*ers 43

这绝对是Python 3.6.0b1中引入的错误.iter()最近的实现已经切换到使用_PyObject_FastCall()(优化,参见问题27128),并且必须是这个打破这个的调用.

同样的问题与classmethodArgument Clinic解析支持的其他C 方法有关:

>>> 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
Run Code Online (Sandbox Code Playgroud)

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

from functools import partial

j = iter(partial(datetime.datetime.now), None)
Run Code Online (Sandbox Code Playgroud)

我提交了问题30524 - iter(classmethod,sentinel)为Argument Clinic类方法打破了吗?与Python项目.对此的修复已落地并且是3.6.2rc1的一部分.

  • @erip:因为它*更快*.没有要创建的Python框架,C实现可以直接调用`datetime.datetime.now` C函数. (10认同)
  • +1,但作为一个兴趣点,为什么你更喜欢`partial`之类的东西,比如一个`lambda`(就像你的评论中一样)? (9认同)

MSe*_*ert 16

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

在这种情况下,罪犯是PyObject_Call_PyObject_CallNoArg相当于callable_iterator.__next__(你的对象是a callable_iterator)方法的C 替换.

PyObject_Call不会返回一个新的datetime.datetime,而实例_PyObject_CallNoArg收益NULL(这大致相当于在Python的除外).

通过CPython源代码挖掘一下:

_PyObject_CallNoArg只是一个宏,_PyObject_FastCall而宏又是一个宏_PyObject_FastCallDict.

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

因为datetime.datetime.nowMETH_FASTCALL标志它在第四个结束,case_PyStack_UnpackDict返回NULL,甚至从未调用该函数.

我会停在那里,让Python开发人员弄清楚那里有什么问题.@Martijn Pieters已经提交了一份Bug报告,他们将修复它(我希望他们能很快修复它).

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

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

j = iter(now, None)
next(j)  # datetime.datetime(2017, 5, 31, 14, 23, 1, 95999)
Run Code Online (Sandbox Code Playgroud)

  • 这是FASTCALL优化中确认的错误,Victor Stinner在https://github.com/python/cpython/pull/1886上有一个初步补丁. (3认同)