Есть реализация функции zip и enumerate. Как можно упростить код?
def zi(*iterables):
length = min(map(len, iterables))
result = []
for index in range(length):
new_item = tuple(map(lambda item: item[index], iterables))
result.append(new_item)
return result
def enumerat(iterable, start=0):
result = []
for index in range(len(iterable)):
new_item = (index + start, iterable[index])
result.append(new_item)
return result
Ответы (2 шт):
основной вопрос - "ЗАЧЕМ" :)
Ну тем не менее можно упросить код так:
def zi(*iterables):
return [tuple(map(lambda item: item[index], iterables)) for index in range(min(map(len, iterables)))]
и
def enumerat(iterable, start=0):
return [(index + start, iterable[index]) for index in range(len(iterable))]
P.S.
используя функцию zip можно сильно сократить функцию enumerat:
def enumerat(iterable, start=0):
return zip(range(start, start + len(iterable)), iterable)
Прежде чем говорить об упрощении надо обсудить функциональность. Выдержит ли enumerat такое применение?
import sys
for i, line in enumerate(sys.stdin, start=1):
print(f'{i}: {repr(line)}')
$ python zip-sample.py < zip-sample.py 1: 'import sys\n' 2: '\n' 3: '\n' 4: 'for i, line in enumerate(sys.stdin, start=1):\n' 5: " print(f'{i}: {repr(line)}')\n"
sys.stdin поддерживает перечисление строк, но функция len к нему не применима (длина входного потока не известна) и обращаться по индексу тоже нельзя. Ваш вариант работает только со списками и кортежами (tuple) но не с любыми iterable. В документации приводится что-то вроде референсной реализации enumerate:
def enumerate_(sequence, start=0):
n = start
for elem in sequence:
yield n, elem
n += 1
Этот вариант проще и работает с любым iterable.
С zip ситуация ещё хуже. Даже в документации примера нет.
def zip_(*iterables):
class ZipExit(Exception):
pass
def next_(it):
try:
return next(it)
except StopIteration:
raise ZipExit
# iterators = tuple(map(iter, iterables))
iterators = tuple(iter(it) for it in iterables)
try:
while True:
# yield tuple(map(next_, iterators))
yield tuple(next_(it) for it in iterators)
except ZipExit:
pass
Тут требуются объяснения. Самый общий протокол для iterable выглядит так:
iterator = iter(interable) # создаём итератор v0 = next(iterator) # извлекаем значения v1 = next(iterator) ... vn = next(iterator) next(iterator) # когда данные закончились # next бросает исключение StopIteration
Так как мы хотим обработать любые последовательности (iterable), следует использовать этот протокол.
Исключение ZipExit нужно так как это единственный способ сообщить об ошибке (кончились данные) при вычислении аргумента вызова tuple. Хочется бросать и ловить StopIteration но язык это явно запрещает. А любое другое стандартное исключение нельзя использовать так как такое же исключение можно получить при вызове next(it), который может привести к исполнению кода на Python с произвольной логикой внутри. Поэтому уникальный тип исключения.
Вызовы map я поместил в комментарии на случай если вы хотите решение с минимумом обращений к глобальным функциям Python.
Если использовать map можно, то есть вариант проще:
def zip_(*iterables):
iterators = tuple(map(iter, iterables))
while True:
t = tuple(map(next, iterators))
if len(t) != len(iterators):
break
yield t
Тут есть одно тонкое место: вызов next рано или поздно должен привести к выбрасыванию StopIteration и код завершится с ошибкой - из генераторов, а zip_ - генератор, бросать StopIteration нельзя. И однако этот код работает благодаря тому что вызов next делается в теле map, который перехватывает StopIteration (и только его) и возвращает не полный кортеж. Что проверяется и у нас есть шанс завершить zip_ нормально.
Хотя этот код выглядит просто, устроен он сложно. Я бы не рекомендовал это в качестве нормальной практики.