Комментарии 58
Используйте циклы вместо reduce.
Чем он их не устраивает? Если используются функции из operator или что-нибудь подобное, то код выглядит очень понятно и коротко, как и с map или filter.
Ну, читал, что reduce не стоит использовать т.к. он слишком запутанно работает. И схема его работы не всегда является прозрачной, особенно при использовании лямбда-функций.
При желании и генераторы списков можно писать трёхэтажные — потом в них чёрт ногу сломит. Аккуратный reduce — это красиво, коротко, ясно. Я за reduce :)
Да и я по большому счету тоже:)
Ключевое слово аккуратный, я тоже стараюсь использовать map, filter, reduce, zip аккуратно, и смотрится достаточно красиво (для меня по крайней мере). А как Вы, например, относитесь к строчке кода, в которой вместе используется reduce, zip, генератор списка и еще что-нибудь — такие участки кода порой встречаются. Мне они не всегда кажутся очевидными.
reduce целенаправленно выдавливают из языка. В 3 версии он уже выкинут в модуль functools. У меня сложилось впечатление, что Гвидо считает использование функциональных возможностей не «питонистическим» подходом.
Эту идею продвигает и Гвидо ван Россум, потому что считает, что использование reduce'а в целом сложнее для восприятия чем использование цикла. Кроме того он несколько проигрывает по производительности, поэтому в третьей версии Python он был вынесен из стандартной библиотеки в модуль functools.
Почитать про это подробнее можно в этом посте.
Почитать про это подробнее можно в этом посте.
Кроме того он несколько проигрывает по производительности
from time import time
from functools import reduce
from operator import add
lst = range(1,10000000)
r1, r2 = 0, 0
t1 = time()
r1 = reduce(add, lst)
t1 = time() - t1
t2 = time()
for i in lst:
r2 = add(i, r2)
t2 = time() - t2
print(t1)
print(t2)
>>> 1.0490000248
>>> 2.14700007439
Это если вы используете модуль
operator
. Если нужно создавать свою лямбду, то вариант с циклом быстрее.Вы имеете ввиду такой вариант:
Но такой вариант очень часто не применим, т.к. нужно использовать уже готовую функцию, а не писать лямбду (или выносить логику в цикл). К тому же, получается, не сильно быстрее:
>qqq.py
1.83299994469
1.74499988556
>qqq.py
1.79699993134
1.79299998283
>qqq.py
1.75500011444
1.81399989128
Но в целом я согласен, код с reduce тяжелее читается.
from time import time
from functools import reduce
lst = xrange(1,10000000)
r1, r2 = 0, 0
t1 = time()
r1 = reduce(lambda x, y: x + y, lst)
t1 = time() - t1
t2 = time()
for i in lst:
r2 = i + r2
t2 = time() - t2
print(t1)
print(t2)
Но такой вариант очень часто не применим, т.к. нужно использовать уже готовую функцию, а не писать лямбду (или выносить логику в цикл). К тому же, получается, не сильно быстрее:
>qqq.py
1.83299994469
1.74499988556
>qqq.py
1.79699993134
1.79299998283
>qqq.py
1.75500011444
1.81399989128
Но в целом я согласен, код с reduce тяжелее читается.
Да, такой вариант. Ниже ответил habrahabr.ru/post/179271/#comment_6264423
Вообще
Вообще
reduce
замечательная вещь, но только в ФП языках. А Python, как ни крути, таковым не является.Но в целом я согласен, код с reduce тяжелее читается
Reduce — это, в первую очередь, идея: вы берёте список и аккумулируете его элементы, получая на выходе одно значение.
For loop — это тоже идея, но другая. For позволяет итерировать по списку, т.е. последовательно получать доступ к каждому элементу.
For можно заменить на reduce, равно как и reduce на for, но по сути это будет реализацией одной фичи через другую — концептуально они останутся разными и предназначенными для разных задач. Сравниете два сценария:
1. В 3Б классе 25 учеников, нужно вывести на экран их имена и рост в сантиметрах.
2. В 3Б классе 25 учеников, нужно найти самого высокого из них.
В первом случае напрашивается цикл:
for p in pupils:
print p.name, p.height
Давайте попробуем прочитать «вслух» этот кусок кода: «для каждого ученика в списке: распечатать его имя и рост». Как раз то, что мы хотели сделать, практически дословное соответсвие условию. Т.е. код читабелен, а readability, как мы помним, counts.
Второй сценарий тоже можно записать в виде цикла:
highest_pupil = None
max_height = 0
for p in pupils:
if p.height > max_height:
highest_pupil = p
max_height = p.height
return highest_pupil
Но давайте и его попробуем прочитать: «присвоить самому высокому ученику значение None; установить максимальную высоту в 0; для каждого ученика в списке: если его высота больше максимальной: присвоить переменной самого выского ученика значение текущего ученика, присвоить максимальной высоте значение высоты текущего ученика». Чёрт, получилось как-то побольше и посложнее, чем было в условии. Но главное, что это описание не отражает идею, а лишь последовательность действий, которые нужно совершить.
Теперь попробуем реализовать второй сценарий через reduce:
reduce(lambda p1, p2: p1 if p1.height > p2.height else p2, pupils)
Или, если лямбды воспринимаются плохо, то так:
def higher_pupil(p1, p2):
if p1.height > p2:
return p1
else:
return p2
reduce(higher_pupil, pupils)
Что можно прочитать как: «Свернуть список учеников, выбирая между двумя из них того, у которого рост выше». Не сказать, что это дословное выражение условия, но уже гораздо, гораздо ближе. И это далеко не предел читабельности. Вот ещё несколько примеров:
1. Имея список инвесторов, найти общую сумму полученных инвестиций:
reduce(lambda s, inv: s + inv.investment, investors)
«Аккумулировать (в возвращаемую переменную s) инвестиции от каждого инвестора».
2. Объединить (т.е., опять же, аккумулировать) строки (или списки):
reduce(lambda result, s: result + s, strings)
и т.д. Другое дело, что ни питоновские лямбды, ни стандартные коллекции, ни общая философия не могут похвастаться хорошей совместимостью с reduce-ом. Возьмём, например, простую задачу подсчёта слов в списке. С точки зрения reduce-а, задача элементарна: нужно всего лишь аккумулировать слова в списке в словарь, добавляя или инкрементируя счётчик для каждого слова. Что-то вроде:
reduce(add_or_inc, words, {})
Проблема возникает именно в этой функции add_or_inc. Однострочные лямбды не позволяют толком использовать if-ы, а defaultdict, почти идеально подходящий для этого случая, работает исключительно императивно. В итоге приходится делать add_or_inc отдельной функцией, что значительно увеличивает размер кода. С другой стороны, for loop, хотя и не выражает идею, но позволяет решить задачу в 3 строчки:
d = defaultdict(int)
for w in words:
d[w] += 1
Так что я бы сказал, что reduce малочитабелен не сам по себе, а именно в контексте существующей инфраструктуры языка. В других языках или в контексте отдельных библиотек он может помогать писать очень и очень читабельный код.
Думаю речь о нечитабельности reduce шла именно в контексте питона, для всех упомянутых вами задач есть решения более «питонистские».
Менее покладистая задача, если хочется написать красиво выполняется лишняя работа:
Либо можно выводить максимальный рост, при необходимости находя такого ученика (хотя конечно получается два прохода вместо одного):
И кстати, ваш цикл можно сократить на две строчки получив «читабельное» выражение:
Т.е. «берём первого попавшегося и сравниваем по высоте с остальными».
Иначе говоря, сам язык и его инструментарий не очень располагает к reduce'ам и другим функциональным приёмам, посему мне кажется вполне логично что их убирают в закрома библиотек.
Имея список инвесторов, найти общую сумму полученных инвестиций
sum([inv.investment for inv in investors])
Объединить (т.е., опять же, аккумулировать) строки (или списки)
''.join(strings)
В 3Б классе 25 учеников, нужно найти самого высокого из них.
Менее покладистая задача, если хочется написать красиво выполняется лишняя работа:
pupils.sort(key=lambda pupil: pupil.height)
pupils[0]
Либо можно выводить максимальный рост, при необходимости находя такого ученика (хотя конечно получается два прохода вместо одного):
max_height = max([pupil.height for pupil in pupils])
highest_pupil = [for pupil in pupils if pupil.height==max_height][0]
И кстати, ваш цикл можно сократить на две строчки получив «читабельное» выражение:
highest_pupil = pupils[0]
for pupil in pupils:
if pupil.height > highest_pupil.height:
highest_pupil = pupil
return highest_pupil
Т.е. «берём первого попавшегося и сравниваем по высоте с остальными».
Иначе говоря, сам язык и его инструментарий не очень располагает к reduce'ам и другим функциональным приёмам, посему мне кажется вполне логично что их убирают в закрома библиотек.
Я ещё раз повторюсь: в reduce-е важна идея — взять все элементы и собрать по ним некую «статистику». Это как с for (сишным) и foreach — любой foreach можно элементарно развернуть в for, но если вам нужно обработать каждый элемент списка, то вы используете foreach. Так вы явно декларируете своё намерение, тем самым повышая читабельность.
Что касается примеров, то попробуйте чуть-чуть усложнить задачу и более «питоновские» решения сломаются. Например, что делать, если нам нужна не сумма инвестиций, а имя самого активного инвестора? Встроенные функции типа sum уже на сработают. Или вернуть не просто сумму, а объект InvestmentStats, который собирает сразу несколько статистик (учтите, что список инвесторов большой и читается с диска, так что делать несколько проходов — не comme il faut). Или нужно объединить списки/словари, а не строки. Или объединить список векторов в матрицу. Я могу приводить примеры бесконечно.
Что касается самого высокого ученика, то ваше решение некорректно: если список учеников пуст, то `highest_pupil = pupils[0]` выдаст ошибку. А значит надо проверять два кейса — список пусть или не пуст, а значит код ещё больше расширяется и становится ещё менее понятным (эй, мне нужно было просто найти самого высокого ученика! какие ещё дополнительные условия?). reduce прекрасно обрабатывает и этот случай (правда, в мой код нужно добавить начальное значение, но кейс так или иначе покрыт).
Ну и да, проходить по списку несколько раз или, тем более, сортировать его только для того, чтобы найти самый большой элемент — это ну совсем-совсем нехорошо.
Насчёт того, что стандартный инструментарий не располагает к использованию reduce-а — согласен, примерно это я и писал в предыдущем комментарии. Другой вопрос, стоило ли выкидывать reduce или следовало ещё раз подумать над инструментарием. Всё-таки reduce описывает довольно распространённый паттерн, а Гвидо вместо него предлагает ввести набор частных решений, таких как sum(), product(), any(), all() и другие.
Что касается примеров, то попробуйте чуть-чуть усложнить задачу и более «питоновские» решения сломаются. Например, что делать, если нам нужна не сумма инвестиций, а имя самого активного инвестора? Встроенные функции типа sum уже на сработают. Или вернуть не просто сумму, а объект InvestmentStats, который собирает сразу несколько статистик (учтите, что список инвесторов большой и читается с диска, так что делать несколько проходов — не comme il faut). Или нужно объединить списки/словари, а не строки. Или объединить список векторов в матрицу. Я могу приводить примеры бесконечно.
Что касается самого высокого ученика, то ваше решение некорректно: если список учеников пуст, то `highest_pupil = pupils[0]` выдаст ошибку. А значит надо проверять два кейса — список пусть или не пуст, а значит код ещё больше расширяется и становится ещё менее понятным (эй, мне нужно было просто найти самого высокого ученика! какие ещё дополнительные условия?). reduce прекрасно обрабатывает и этот случай (правда, в мой код нужно добавить начальное значение, но кейс так или иначе покрыт).
Ну и да, проходить по списку несколько раз или, тем более, сортировать его только для того, чтобы найти самый большой элемент — это ну совсем-совсем нехорошо.
Насчёт того, что стандартный инструментарий не располагает к использованию reduce-а — согласен, примерно это я и писал в предыдущем комментарии. Другой вопрос, стоило ли выкидывать reduce или следовало ещё раз подумать над инструментарием. Всё-таки reduce описывает довольно распространённый паттерн, а Гвидо вместо него предлагает ввести набор частных решений, таких как sum(), product(), any(), all() и другие.
Для меня лично, основное преимущество
foreach
относительно for
— отсутствие необходимости в изменяемой «вручную» индексной переменной: меньше «менеджимых» програмимстом операторов — меньше потенциальных мест для ошибки.Что касается самого высокого ученика, то ваше решение некорректно: если список учеников пуст, то `highest_pupil =pupils[0]` выдаст ошибку.Тогда корректность самой задачи сомнительна. SQL, насколько я помню,
max()
в этом случае выдаст null
— когда впервые увидел, был удивлён. Корректность применения фрагмента max_height = 0
перед циклом в этом случае тоже дискуссионна.«Ручное» аккумулирование и «ручное» задание первоначального значения — тоже потенциальные места для ошибки, если совсем строго подходить.
Для меня лично, основное преимущество foreach относительно for — отсутствие необходимости в изменяемой «вручную» индексной переменной: меньше «менеджимых» програмимстом операторов — меньше потенциальных мест для ошибки.
Дело не только в ошибках — они происходят на этапе написания кода. Дело в понимании кода на этапе перечитывания, и Python делает на этом очень большой упор. Из PEP8:
One of Guido's key insights is that code is read much more often than it is written. The guidelines provided here are intended to improve the readability of code and make it consistent across the wide spectrum of Python code.
Тогда корректность самой задачи сомнительна. SQL, насколько я помню, max() в этом случае выдаст null — когда впервые увидел, был удивлён.
А в чём сомнительность задачи? Вы сомневаетесь, следует ли обрабатывать списки нулевой длины? А что вы тогда предлагаете делать с такими списками, на примере того же SQL — что, по вашему мнению, должна возвращать функция max, если селект вернул пустой result set?
Вы сомневаетесь, следует ли обрабатывать списки нулевой длины?Уже до и за меня решено, что придётся это как-то делать. Сомнительна не задача, а корректность её постановки: что делать в случае пустого списка, не ясно.
А что вы тогда предлагаете делать с такими списками, на примере того же SQL — что, по вашему мнению, должна возвращать функция max, если селект вернул пустой result set?Тогда я ожидал ноль. Но потом понял, что
null
— корректнее: положительность, в общем случае, никем не гарантирована. Ну тут пути три — 1) вернуть ноль (имеет смысл, если тип данных явно задан как положительный) 2) вернуть null
или другое спец.значение 3) кинуть исключение. Выбор за разработчиком исходя из уточнённого смысла задачи.Пардон, но я вообще не понял, что вы хотели сказать. Вы говорите, что корректность постановки задачи про максимальный элемент в (возможно пустом) списке сомнительна, но тут же приводите пример из SQL, где она сформулирована именно так. Так вас устраивает постановка задачи и её решение в SQL или нет? Если да, то в чём тогда проблема с моей формулировкой? Если нет, то какую вы предлагаете альтернативу, на примере того же SQL?
Сомнительна в случае, если список может быть пустой, и не указано, что в этом случае если список пустой. В исходном варианте в вышележащих комментариях было «В 3Б классе 25 учеников», так что явно указано, что можно не закладываться на возможность пустого списка.
Вы опять противоречите себе: сначала вы говорите, что до и за вас решено, что пустой список тоже придётся как-то обрабатывать, а теперь ссылаетесь на конкретный пример про 25 (что явно не 0) учеников. Давайте так: если вам действительно интересна эта тема, то скажите, что конкретно в моих примерах вас не устраивает и как бы вы их реализовали; иначе я считаю это троллингом и выхожу из дискуссии.
Дабы не быть голословным (python 3.3)
В общем быстрее тот код, где меньше вызовов pyhon-функций. Reduce+operator хорошо, свои лямбды — не очень.
Скрытый текст
from functools import reduce
from timeit import timeit
import operator
def t0():
return sum(lst)
def t1():
return reduce(operator.add, lst)
def t2():
a = 0
for b in lst:
a += b
return b
def t3():
return reduce(lambda x, y: x + y, lst)
timeit(t0, number=100)
# 0.7890550769952824
timeit(t1, number=100)
# 3.882541635997768
timeit(t2, number=100)
# 3.9364478749994305
timeit(t3, number=100)
# 7.679830512999615
В общем быстрее тот код, где меньше вызовов pyhon-функций. Reduce+operator хорошо, свои лямбды — не очень.
Ошибок и опечаток в тексте многовато.
Неплохая подборка, спасибо. Хотел бы высказаться по поводу использования map() filter(). Слышал в некоторых источниках, что не следует их мешать с циклом for. Т.е. например, если есть метод/функция и в нём используется цикл for, то если существует еще какая-то логика в данном методе, то её тоже следует писать с циклом for, а не использовать map, который вполне может заменить использование цикла. Данные идеи исключельно для некоторой цельности и выдерживания кода некоторой единой нотации. Соответственно и обратное замечание, что если уж используется map в логике метода/функции, то и остальную логику следует писать с использованием map, filter и т.д… Весьма спорно, но всё же. Согласны?
Согласен. Но я вот стою на том, что функциональности нужно использовать там, где им пристало быть исконно. Например, отсев каких-то значений в списке, преобразование каждого элемента в списке и т.д. А уж for хорошо испльзовать для сложной логики, тем более, что функциональщина в Python быстрее отрабатывает.
map() и filter() в Python можно полностью заменить на list comprehension, и это будет смотреться вполне прилично — в обоих случаях используется for, но в list comprehension ещё дополнительно показывается, что на выходе должен получится список.
В отличие от
LISP
, в Python
есть синтаксис;-) (в смысле, кроме деревьев), в том числе и специальные идиомы языка, являющиеся аналогами этих функций.Картинка по запросу «кодирование»


> Используйте PyChecker для проверки своего кода.
PyChecker компилирует и выполняет код, на мой взгляд лучше pylint использовать.
PyChecker компилирует и выполняет код, на мой взгляд лучше pylint использовать.
В PyCharm последней версии идёт проверка на лету на pep8, очень так удобно.
pylint разве не компилирует?
На текущем проекте используем flake8. Он тоже опрятности способствует, особенно будучи вписанным в предкоммитном хуке.
В опросе не хватает пункта: «Не согласен с положениями GSG частично или полностью»
def ToFloat(Arg, Len=4):
return "".join([To2Hex(ord(X)) for X in (struct.pack(">f", Arg)[0:Len])][::-1])
гы гы гы. pychecker на этом упал
return "".join([To2Hex(ord(X)) for X in (struct.pack(">f", Arg)[0:Len])][::-1])
гы гы гы. pychecker на этом упал
result = []
for x in range(10):
for y in range(5):
if x * y > 10:
result.append((x, y))
vs
result = [(x, y) for x in range(10) for y in range(5) if x * y > 10]
По-моему, второй вариант читается на порядок проще и быстрее. А когда я встречаю 4хэтажную конструкцию, то я сразу хочу ее развидеть.
Для такого линейного случая — прекрасная альтернатива! Но если логика сложнее, то лучше воздержаться от инлайновых вычислений.
Статья — гуд. Реквестирую PDF.
Было интересно, спасибо за перевод. Жду продолжения!
6 лет прошло в оригинальной статье есть изменения
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Советы Google по кодированию на языке Python. Часть первая: советы по программированию