Подтвердить что ты не робот

Iter() не работает с datetime.now()

Простой фрагмент в Python 3.6.1:

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

возвращает:

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

вместо того, чтобы печатать классическое поведение now() с каждым next().

Я видел аналогичный код, работающий в Python 3.3, мне что-то не хватает или что-то изменилось в версии 3.6.1?

4b9b3361

Ответ 1

Это определенно ошибка, появившаяся в Python 3.6.0b1. Реализация 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)

Я подал issue 30524 - iter (classmethod, sentinel), разбитый на методы класса Argument Clinic? с проектом Python. Исправление для этого приземлилось и является частью 3.6.2rc1.

Ответ 2

Я предполагаю, что вы используете CPython, а не другую реализацию Python. И я могу воспроизвести проблему с CPython 3.6.1 (у меня нет PyPy, Jython, IronPython,... поэтому я не могу проверить их).

В этом случае правонарушителем является замена PyObject_Call с _PyObject_CallNoArg в C-эквиваленте метода callable_iterator.__next__ (ваш объект является callable_iterator).

PyObject_Call возвращает новый экземпляр datetime.datetime, а _PyObject_CallNoArg возвращает NULL (что примерно эквивалентно исключению в Python).

Копайте бит через исходный код CPython:

_PyObject_CallNoArg - это просто макрос для _PyObject_FastCall, который, в свою очередь, является макросом для _PyObject_FastCallDict.

Эта функция _PyObject_FastCallDict проверяет тип функции (C -функция или функция Python или что-то еще) и делегирует на _PyCFunction_FastCallDict в этом случае, поскольку datetime.now является функцией C.

Так как datetime.datetime.now имеет флаг METH_FASTCALL, он заканчивается в четвертом case, но там _PyStack_UnpackDict возвращает NULL и функция никогда не называется.

Я остановлюсь на этом, и пусть разработчики Python поймут, что там не так. @Martijn Pieters уже подали отчет об ошибке, и они исправит его (я просто надеюсь, что они исправит его скоро).

Итак, это ошибка, введенная в 3.6, и пока она не исправлена, вам нужно убедиться, что метод не является 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)