Ростислав Дзинько
Переходим на Python 3. Где же ты, reduce?
Это мой второй пост об освоении Python 3. Начался он с того, что захотелось мне использовать всем известную встроенную функцию reduce, а я вместо рабочего кода получил NameError. Оказывается в Python 3 она уже не встроенная, а находится в module functools, в который, начиная с версии Python 2.5, всунули несолько полезностей для работы с объектами-функциями. То есть теперь функцию reduce нужно импортировать.
from functools import reduce
Стоит заметить, что спецификация функции не поменялась, работает она точно также как и во втором питоне. Постал вопрос: "Зачем?". (Более подробно о reduce читаем в документации).
С чего все началось?
А началось все с Гвидо ван Россума, сделавшего следующее высказывание, когда Python 3k только начинали делать. Вот вольный перевод:
Около 12 лет назад в Python появились lambda, reduce(), filter() и map(); появились они с соизволения (мне кажется) Lisp-хакера, которому не хватало их в Python, и который предоставил работающие патчи. Но, невзирая ни на что, я думаю, что эти вещи нужно вырезать из Python 3000.
Также известно, что Гвидо считает эти вещи ненужными, так как есть так называемые "list comprehensions", то есть конструкции типа:
>>> [i * 2 for i in my_list if i > 0]
Вот мнение "великодушного диктатора" о reduce:
Теперь о reduce(). На самом деле это то, что я ненавижу больше всего, потому что кроме нескольких примеров с + или *, почти всегда, когда я вижу вызов reduce() с нетривиальной функцией, мне нужно брать ручку и бумагу, чтобы нарисовать диаграму того, что же действительно передается в функцию перед тем, как понимаю, для чего на самом деле здесь использовалась reduce(). Так что, по-моему, reduce() - практически ограничена ассоциативными операторами, и во всех других случаях лучше сделать явный кумулятивный цикл.
Нужна ли reduce вообще?
Просмотрев свой код и вижу, что за 3 с лишним года работы с Python я использовал reduce, в отличие от map, filter и lambda, очень редко. Задумываясь о различных способах реализации того или иного блока кода, можно увидеть массу случаев, где нужно применить reduce и в большинстве из них находятся альтернативные решения, которые делают код более понятным и читабельным. Рассмотрим несколько примеров на простом списке:
>>> v = [0,1,2,3,4]
Суммирование:
>>> r = reduce(lambda x, y: x + y, v)>>> print(r)10
Ествественно, такое никому не нужно, когда есть sum:
>>> sum(v)>>> 10
Рассмотрим умножение:
>>> v = [1, 2, 3, 4]>>> reduce(lambda x, y: x * y, v)>>> 24>>> r = 1>>> for i in v:>>> r *= i>>> 24
Здесь вариант с reduce выглядит более чем привлекательным.
Соединение списков:
>>> reduce(list.__add__, [[1, 2, 3], [4, 5], [6, 7, 8]], [])[1, 2, 3, 4, 5, 6, 7, 8]>>> from itertools import chain>>> list(chain([1, 2, 3], [4, 5], [6, 7, 8]))[1, 2, 3, 4, 5, 6, 7, 8]
По-моему, вариант с itertools является более понятным и, что гораздо интереснее, возвращает не список, а ... догадайтесь сами. Для других задач зачастую находятся более красивые, или более читабельные решения, например, для логических - использование функций any и all.
reduce для меня и заключение
Иногда с использованием reduce замечаю, что через некоторое время код воспринимается тяжелее, особенно, если это не тривиальное использование оператора, а reduce помешанная с множественными вложенными вызовами функций, поэтому стараюсь применять reduce там, где это действительно упрощает код и делает его красивее, а такие ситуации, по-моему, возникают очень редко и должны быть видны сразу: если уже задумываешся, значит что-то не так, что-то некрасиво.
Вообще-то я поддерживаю решение вынести reduce из разряда встроенных функций в модуль functools, теперь, перед тем как нагадить в коде дважды подумаю, ну для этого ж нужно еще один дополнительный импорт! :)
Всем спасибо за внимание, кастую холиварщиков в комменты...
- 03 Фев 13:05
