2017-05-31 26 views
43

一個簡單的代碼片段工作在Python 3.6.1:ITER()不datetime.now()

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中有變化嗎?

+2

有趣的是,我期望這個工作。它在3.4和3.5也可以工作。 –

+2

當你用'lambda:datetime.datetime.now()'或'partial(datetime.datetime.now)'替換'datetime.datetime.now'時,它可以工作。 –

+2

我想你應該在他們的[bug跟蹤器](https://bugs.python.org/)上報告這個。 – MSeifert

回答

43

這絕對是Python 3.6.0b1中引入的一個bug。 iter()實現最近切換到使用_PyObject_FastCall()(一個優化,請參閱issue 27128),它必須是這個打破這一點的調用。

同樣的問題棱與論證診所解析支持其他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的一部分。

+9

+1,但作爲一個興趣點,爲什麼你更喜歡'partial'到像'lambda'這樣的東西?(比如在你的評論中)? – erip

+10

@erip:因爲*更快*。沒有Python框架可以創建,C實現可以直接調用'datetime.datetime.now' C函數。 –

16

我假設你使用CPython而不是另一個Python實現。我可以用CPython 3.6.1(我沒有PyPy,Jython,IronPython,...所以我不能檢查這些)重現問題。

在這種情況下,罪犯是將PyObject_Call_PyObject_CallNoArg替換爲callable_iterator.__next__(您的對象是callable_iterator)方法的C當量。

PyObject_Call確實會返回一個新的datetime.datetime實例,而_PyObject_CallNoArg返回NULL(這大致相當於Python中的異常)。

挖通過CPython的源代碼中的位:

_PyObject_CallNoArg僅僅是_PyObject_FastCall宏這又爲_PyObject_FastCallDict一個宏。

This _PyObject_FastCallDict function檢查功能的類型(C -function或Python函數或其他內容)在這種情況下,因爲datetime.now是一個C函數和代表給_PyCFunction_FastCallDict

由於datetime.datetime.nowMETH_FASTCALL標誌它結束了第四case_PyStack_UnpackDict回報NULL和功能甚至從來沒有叫。

我會在那裏停下來讓Python開發者找出那裏有什麼問題。 @Martijn Pieters已經提交了一個Bug報告,他們會解決它(我希望他們能儘快修復)。

所以這是他們在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) 
+1

偉大的回答也增加了對罪魁禍首的洞察力。我會在Martijn的答案上保留已接受的標記,因爲他已經提交了Python項目的問題。 – Vidak

+3

這是FASTCALL優化中的一個確認錯誤,Victor Stinner在https://github.com/python/cpython/pull/1886有一個初步補丁 –