google web font

понедельник, 14 марта 2016 г.

Сколькими способами можно развернуть генератор в список в Python3

Одно из заметных нововведений в Python3 по сравнению с Python2 это то, что встроенные функции filter, map, range и zip теперь возвращают итераторы. В некоторых случаях этот итератор может занимать больше памяти, чем тот список, который от него хотят. А если их ещё и засовывать один в другой, вроде чего-то такого:
def increase(self, *increments):
    self.values = map(sum, zip(self.values, increments))
так вообще никакой памяти не хватит. Отсюда, конечно вопрос: как развернуть этот итератор в нормальную последовательность? Как бы сильно это ни противоречило Дзену Пайтона, способов много.
Самый очевидный вариант:
def increase(self, *increments):
    self.values = tuple(map(sum, zip(self.values, increments)))
Слишком просто, чтобы писать об этом блог-пост. С таким же успехом можно было вообще избавиться от map, использовав list-comprehension:
def increase(self, *increments):
    self.values = [sum(x) for x in zip(self.values, increments)]
(обязательно квадратные скобки, а то генератор никуда не денется :-)
В общем, есть более экзотические варианты.

Итераторы, как и любой iterable-объект, можно раскрыть с помощью звёздочки. Конечно, нельзя просто поставить звёздочку и получить список, но для начала можно попробовать раскрыть этот итератор как мы это делаем каждый день:
self.increase(self, *increments):
    self.set_values(*map(sum, zip(self.values, increments)))

def set_values(self, *values):
    self.values = values
Здесь мы используемый общеизвестный приём, когда последовательность при передаче аргументов функции/метода с помощью звездочки раскрывается в отдельные "позиционные" аргументы, а уже функция объявлена так, что произвольное число аргументов, с помощью той же звёздочки, собирается обратно в один список.

Но можно обойтись и без лишнего метода. Примерно всё то же самое, но итератор раскрывается не в аргументы метода, а прямо в список:
def increase(self, *increments):
    self.values = [*map(sum, zip(self.values, increments))]
Мы создаём список с помощью квадратных скобок, а вот внутри них уже можно использовать звёздочку. Аналогичным образом вместо списка можно получить set:
def increase(self, *increments):
    self.values = {*map(sum, zip(self.values, increments))}
а вот если нужен кортеж (tuple), то синтаксис уже повеселее:
def increase(self, *increments):
    self.values = (*map(sum, zip(self.values, increments)),)
Запятая в конце, после вызова map, превращает простые скобки в объявление кортежа. Хотя, как раз-таки именно скобки для объявления кортежа и не требуются:
def increase(self, *increments):
    self.values = *map(sum, zip(self.values, increments)),
Насколько этот вариант экзотический? Ну, скажем так, бывает поэкзотичнее.

Вот, например, возможность присвоить последовательность элементам другой последовательности, которая формально выглядит как-то так:
a, b, c = range(3)
(a, b, c) = range(3)
[a, b, c] = range(3)
Это только в простом варианте она присваивает по одному значению каждому элементу. А ведь можно и вот так:
def increase(self, *increments):
    [*self.values] = map(sum, zip(self.values, increments))
По аналогии с произвольным числом позиционных аргументов, здесь производится захват нескольких значений в один элемент, который становится списком, даже если map не вернёт ни одного элемента. Для ценителей есть самый экзотичный вариант:
def increase(self, *increments):
    *self.values, = map(sum, zip(self.values, increments))
Здесь список заменён на кортеж (без скобочек). self.values при этом остаётся списком, конечно же.

Дену Пайтона, на мой взгляд, больше всего соответствует самый первый вариант, когда map явно оборачивается в tuple. Но некоторые могут предпочесть list comprehension.

Я надеюсь, что на этом "относительно адекватные" (в одну строчку) способы превратить итератор или генератор в список заканчиваются. Если есть другие – буду рад узнать, пишите в комментарии.

Комментариев нет:

Отправить комментарий