Я являюсь автором вопроса, и я хотел бы кратко изложить здесь то, что я узнал и что я думаю, осталось. (Я не планирую публиковать его как новый вопрос.)
В Python, StopIteration
из метода __next__
итератора трактуется как сигнал о том, что итератор достиг конца , (В противном случае это сигнал об ошибке.) Таким образом, метод итератора __next__
должен перехватывать все StopIteration
, что не является сигналом конца.
Создается объект карты с кодом вида map(func, *iterables)
, где func
является функцией, а *iterables
обозначает конечную последовательность из одного (по состоянию на Python 3.8.1) или более итераций. Существует (как минимум) два вида подпроцесса процесса __next__
результирующего объекта карты, который может вызвать StopIteration
:
- Процесс, в котором метод
__next__
одного из итераций в вызывается последовательность *iterables
. - Процесс, в котором вызывается аргумент
func
.
Намерение map
, как я понимаю из его документа (или отображается help(map)
) означает, что StopIteration
из подпроцесса вида (2) НЕ является концом объекта карты. Однако текущее поведение __next__
объекта карты таково, что в этом случае его процесс генерирует StopIteration
. (Я не проверял, действительно ли он ловит StopIteration
или нет. Если это так, то в любом случае он снова поднимает StopIteration
.) Это является причиной проблемы, о которой я спрашивал.
В ответе выше, user2357112 поддерживает Monica (позвольте мне дружелюбно сокращать название до «User Primes»), находит последствия этого уродства, но ответил, что это ошибка Python, а не map
. К сожалению, я не нахожу убедительной поддержки этого вывода в ответе. Я подозреваю, что исправление map
было бы лучше, но некоторые другие люди, похоже, не согласны с этим по соображениям производительности. Я ничего не знаю о реализации встроенных функций Python и не могу судить. Так что этот вопрос оставлен для меня. Тем не менее, ответ пользователя Primes был достаточно информативным, чтобы оставить левый вопрос для меня неважным. (Спасибо, user2357112 снова поддерживает Монику!)
Кстати, код, который я пытался опубликовать в комментарии к ответу пользователя, выглядит следующим образом. (Я думаю, что это сработало бы до PEP 479.)
def map2(function, iterable):
"This is a 2-argument version for simplicity."
iterator = iter(iterable)
while True:
arg = next(iterator) # StopIteration out here would have been propagated.
try:
yield function(arg)
except StopIteration:
raise RuntimeError("generator raised StopIteration")
Ниже приведена немного другая версия этого (опять же, версия с двумя аргументами), которая может быть более удобной (опубликовано с надеждой получать предложения по улучшению!).:
import functools
import itertools
class StopIteration1(RuntimeError):
pass
class map1(map):
def __new__(cls, func, iterable):
iterator = iter(iterable)
self = super().__new__(cls, func, iterator)
def __next__():
arg = next(iterator)
try:
return func(arg)
except StopIteration:
raise StopIteration1(0)
except StopIteration1 as error:
raise StopIteration1(int(str(error)) + 1)
self.__next__ = __next__
return self
def __next__(self):
return self.__next__()
# tuple(map1(tuple,
# [map1(next,
# [iter([])])]))
# ---> <module>.StopIteration1: 1