2017-05-31 20 views
43

Một đoạn đơn giản bằng Python 3.6.1:iter() không làm việc với datetime.now()

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

lợi nhuận:

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

thay vì in ra now() hành vi cổ điển với mỗi next() .

Tôi đã nhìn thấy mã tương tự hoạt động trong Python 3.3, tôi có thiếu thứ gì đó hoặc có điều gì đó đã thay đổi trong phiên bản 3.6.1 không?

+2

Thú vị, tôi hy vọng điều này sẽ hoạt động. Nó hoạt động trong 3,4 và 3,5 quá. –

+2

Nó hoạt động khi bạn thay thế 'datetime.datetime.now' bằng' lambda: datetime.datetime.now() 'hoặc' partial (datetime.datetime.now) '. –

+2

Tôi đoán bạn nên báo cáo điều này tại [bug tracker] của họ (https://bugs.python.org/). – MSeifert

Trả lời

43

Đây chắc chắn là lỗi được giới thiệu trong Python 3.6.0b1. Triển khai iter() gần đây đã chuyển sang sử dụng _PyObject_FastCall() (tối ưu hóa, xem issue 27128) và phải là cuộc gọi này đang vi phạm điều này.

Các arrises cùng một vấn đề với các phương pháp C classmethod khác được hỗ trợ bởi Argument Clinic phân tích cú pháp:

>>> 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 

Nếu bạn cần một công việc xung quanh, quấn callable trong một đối tượng functools.partial():

from functools import partial 

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

tôi đã nộp issue 30524 -- iter(classmethod, sentinel) broken for Argument Clinic class methods? với dự án Python. Việc sửa chữa cho điều này đã hạ cánh và là một phần của 3.6.2rc1.

+9

+1, nhưng là một điểm đáng chú ý, tại sao bạn lại muốn 'một phần' thành một cái gì đó giống như, ví dụ, một' lambda' (như trong bình luận của bạn)? – erip

+10

@erip: vì nó * nhanh hơn *. Không có khung Python nào được tạo, việc triển khai C có thể gọi hàm 'datetime.datetime.now' C trực tiếp. –

16

Tôi giả sử bạn đang sử dụng CPython chứ không phải triển khai Python khác. Và tôi có thể tái sản xuất vấn đề với CPython 3.6.1 (Tôi không có PyPy, Jython, IronPython, ... vì vậy tôi không thể kiểm tra những điều này).

Người phạm tội trong trường hợp này là thay thế PyObject_Call bằng _PyObject_CallNoArg trong tương đương C của callable_iterator.__next__ (đối tượng của bạn là phương thức callable_iterator).

PyObject_Call trả về datetime.datetime trường hợp mới trong khi _PyObject_CallNoArg trả về NULL (tương đương với ngoại lệ trong Python).

Đào một chút thông qua mã nguồn CPython:

Các _PyObject_CallNoArg chỉ là một vĩ mô cho _PyObject_FastCall do đó là một vĩ mô cho _PyObject_FastCallDict.

This _PyObject_FastCallDict function kiểm tra loại chức năng (C-chức năng hoặc hàm Python hoặc cái gì khác) và ủy quyền cho _PyCFunction_FastCallDict trong trường hợp này là datetime.now là hàm C.

Kể từ datetime.datetime.nowMETH_FASTCALL cờ nó kết thúc trong thứ tư case nhưng có _PyStack_UnpackDict lợi nhuận NULL và hàm được thậm chí không bao giờ gọi.

Tôi sẽ dừng ở đó và để cho các nhà phát triển Python tìm hiểu xem có gì sai trong đó. @Martijn Pieters đã gửi một báo cáo lỗi và họ sẽ sửa chữa nó (tôi chỉ hy vọng họ sửa chữa nó sớm).

Vì vậy, đó là lỗi mà họ đã giới thiệu trong 3.6 và cho đến khi được khắc phục, bạn cần đảm bảo phương pháp không phải là CFunction với cờ METH_FASTCALL. Như cách giải quyết bạn có thể quấn nó.Ngoài các khả năng @Martijn Pieters đã đề cập cũng có một cách đơn giản:

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

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

Câu trả lời tuyệt vời nữa, với thông tin chi tiết bổ sung về thủ phạm. Tôi sẽ giữ dấu được chấp nhận trên câu trả lời của Martijn vì anh ta đã gửi vấn đề với dự án Python. – Vidak

+3

Đó là một lỗi xác nhận trong các tối ưu hóa FASTCALL, và Victor Stinner có một bản vá sơ bộ tại https://github.com/python/cpython/pull/1886 –

Các vấn đề liên quan