Мир Python: функционалим по-маленьку | Python для продвинутых
Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером
Введение
Существует несколько парадигм в программировании, например, ООП, функциональная, императивная, логическая, да много их. Мы будем говорить про функциональное программирование.
Предпосылками для полноценного функционального программирования в Python являются: функции высших порядков, развитые средства обработки списков, рекурсия, возможность организации ленивых вычислений.
Сегодня познакомимся с простыми элементами, а сложные конструкции будут в других уроках.
Теория в теории
Как и в разговоре об ООП, так и о функциональном программировании, мы стараемся избегать определений. Все-таки четкое определение дать тяжело, поэтому здесь четкого определения не будет. Однако! Хотелки для функционального языка выделим:
- Функции высшего порядка
- Чистые функции
- Неизменяемые данные
Это не полный список, но даже этого хватает чтобы сделать «красиво».
Если читателю хочется больше, то вот расширенный список:
- Функции высшего порядка
- Чистые функции
- Неизменяемые данные
- Замыкания
- Ленивость
- Хвостовая рекурсия
- Алгебраические типы данных
- Pattern matching
Постепенно рассмотрим все эти моменты и как использовать в Python.
А сегодня кратко, что есть что в первом списке.
Чистые функции
Чистые функции не производят никаких наблюдаемых побочных эффектов, только возвращают результат. Не меняют глобальных переменных, ничего никуда не посылают и не печатают, не трогают объектов, и так далее. Принимают данные, что-то вычисляют, учитывая только аргументы, и возвращают новые данные.
Плюсы:
- Легче читать и понимать код
- Легче тестировать (не надо создавать «условий»)
- Надежнее, потому что не зависят от «погоды» и состояния окружения, только от аргументов
- Можно запускать параллельно, можно кешировать результат
Неизменяемые данные
Неизменяемые (иммутабельные) структуры данных — это коллекции, которые нельзя изменить.
Примерно как числа. Число просто есть, его нельзя поменять. Также и неизменяемый массив — он такой, каким его создали, и всегда таким будет. Если нужно добавить элемент — придется создать новый массив.
Преимущества неизменяемых структур:
- Безопасно разделять ссылку между потоками
- Легко тестировать
- Легко отследить жизненный цикл (соответствует data flow)
theory-source
Функции высшего порядка
Функцию, принимающую другую функцию в качестве аргумента и/или возвращающую другую функцию, называют функцией высшего порядка:
def f(x):
return x + 3
def g(function, x):
return function(x) * function(x)
print(g(f, 7))
Рассмотрели теорию, начнем переходить к практике, от простого к сложному.
Списковые включения или генератор списка
Рассмотрим одну конструкцию языка, которая поможет сократить количество строк кода. Не редко уровень программиста на Python можно определить с помощью этой конструкции.
Пример кода:
for x in xrange(5, 10):
if x % 2 == 0:
x =* 2
else:
x += 1
Цикл с условием, подобные встречаются не редко. А теперь попробуем эти 5 строк превратить в одну:
>>> [x * 2 if x % 2 == 0 else x + 1 for x in xrange(5, 10)] [6, 12, 8, 16, 10]
Недурно, 5 строк или 1. Причем выразительность повысилась и такой код проще понимать — один комментарий можно на всякий случай добавить.
В общем виде эта конструкция такова:
[stmt for var in iterable if predicate]
Стоит понимать, что если код совсем не читаем, то лучше отказаться от такой конструкции.
Анонимные функции или lambda
Продолжаем сокращать количества кода.
Функция:
def calc(x, y):
return x**2 + y**2
Функция короткая, а как минимум 2 строки потратили. Можно ли сократить такие маленькие функции? А может не оформлять в виде функций? Ведь, не всегда хочется плодить лишние функции в модуле.
А если функция занимает одну строчку, то и подавно. Поэтому в языках программирования встречаются анонимные функции, которые не имеют названия.
Анонимные функции в Python реализуются с помощью лямбда-исчисления и выглядят как лямбда-выражения:
>>> lambda x, y: x**2 + y**2 <function <lambda> at 0x7fb6e34ce5f0>
Для программиста это такие же функции и с ними можно также работать.
Чтобы обращаться к анонимным функциям несколько раз, присваиваем переменной и пользуемся на здоровье.
Пример:
>>> (lambda x, y: x**2 + y**2)(1, 4) 17 >>> >>> func = lambda x, y: x**2 + y**2 >>> func(1, 4) 17
Лямбда-функции могут выступать в качестве аргумента. Даже для других лямбд:
multiplier = lambda n: lambda k: n*k
Использование lambda
Функции без названия научились создавать, а где использовать сейчас узнаем. Стандартная библиотека предоставляет несколько функций, которые могут принимать в качестве аргумента функцию — map(), filter(), reduce(), apply().
map()
Функция map() обрабатывает одну или несколько последовательностей с помощью заданной функции.
>>> list1 = [7, 2, 3, 10, 12] >>> list2 = [-1, 1, -5, 4, 6] >>> list(map(lambda x, y: x*y, list1, list2)) [-7, 2, -15, 40, 72]
Мы уже познакомились с генератором списков, давайте им воспользуемся, если длина списков одинаковая:
>>> [x*y for x, y in zip(list1, list2)] [-7, 2, -15, 40, 72]
Итак, заметно, что использование списковых включений короче, но лямбды более гибкие. Пойдем дальше.
filter()
Функция filter() позволяет фильтровать значения последовательности. В результирующем списке только те значения, для которых значение функции для элемента истинно:
>>> numbers = [10, 4, 2, -1, 6] >>> list(filter(lambda x: x < 5, numbers)) # В результат попадают только те элементы x, для которых x < 5 истинно [4, 2, -1]
То же самое с помощью списковых выражений:
>>> numbers = [10, 4, 2, -1, 6] >>> [x for x in numbers if x < 5] [4, 2, -1]
reduce()
Для организации цепочечных вычислений в списке можно использовать функцию reduce().
Например, произведение элементов списка может быть вычислено так (Python 2):
>>> numbers = [2, 3, 4, 5, 6] >>> reduce(lambda res, x: res*x, numbers, 1) 720
Вычисления происходят в следующем порядке:
((((1*2)*3)*4)*5)*6
Цепочка вызовов связывается с помощью промежуточного результата (res). Если список пустой, просто используется третий параметр (в случае произведения нуля множителей это 1):
>>> reduce(lambda res, x: res*x, [], 1) 1
Разумеется, промежуточный результат необязательно число. Это может быть любой другой тип данных, в том числе и список. Следующий пример показывает реверс списка:
>>> reduce(lambda res, x: [x]+res, [1, 2, 3, 4], []) [4, 3, 2, 1]
Для наиболее распространенных операций в Python есть встроенные функции:
>>> numbers = [1, 2, 3, 4, 5] >>> sum(numbers) 15 >>> list(reversed(numbers)) [5, 4, 3, 2, 1]
В Python 3 встроенной функции reduce() нет, но её можно найти в модуле functools.
apply()
Функция для применения другой функции к позиционным и именованным аргументам, заданным списком и словарем соответственно (Python 2):
>>> def f(x, y, z, a=None, b=None):
... print x, y, z, a, b
...
>>> apply(f, [1, 2, 3], {'a': 4, 'b': 5})
1 2 3 4 5
В Python 3 вместо функции apply() следует использовать специальный синтаксис:
>>> def f(x, y, z, a=None, b=None):
... print(x, y, z, a, b)
...
>>> f(*[1, 2, 3], **{'a': 4, 'b': 5})
1 2 3 4 5
На этой встроенной функции закончим обзор стандартной библиотеки и перейдем к последнему на сегодня функциональному подходу.
Замыкания
Функции, определяемые внутри других функций, представляют собой замыкания. Зачем это нужно? Рассмотрим пример, который объяснит:
Код (вымышленный):
def processing(element, type_filter, all_data_size):
filters = Filter(all_data_size, type_filter).get_all()
for filt in filters:
element = filt.
filter(element)
def main():
data = DataStorage().get_all_data()
for x in data:
processing(x, 'all', len(data))
Что можно в коде заметить: в этом коде переменные, которые живут по сути постоянно (т.е. одинаковые), но при этом мы загружаем или инициализируем по несколько раз. В итоге приходит понимание, что инициализация переменной занимает львиную долю времени в этом процессе, бывает что даже загрузка переменных в scope уменьшает производительность. Чтобы уменьшить накладные расходы необходимо использовать замыкания.
В замыкании однажды инициализируются переменные, которые затем без накладных расходов можно использовать.
Научимся оформлять замыкания:
def multiplier(n):
"multiplier(n) возвращает функцию, умножающую на n"
def mul(k):
return n*k
return mul
# того же эффекта можно добиться выражением
# multiplier = lambda n: lambda k: n*k
mul2 = multiplier(2) # mul2 - функция, умножающая на 2, например,
mul2(5) == 10
Заключение
В уроке мы рассмотрели базовые понятия ФП, а также составили список механизмов, которые будут рассмотрены в следующих уроках.
Поговорили о способах уменьшения количества кода, таких как cписковые включения (генератор списка), lamda функции и их использовании и на последок было несколько слов про замыкания и для чего они нужны.
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты.
4. Условия подведения итогов \ КонсультантПлюс
4. Условия подведения итогов
1. Спортивные соревнования в дисциплине «городки классические» проводятся на 90 фигур в три тура из двух партий по 15 фигур в каждом туре. Проходной норматив во 2 и 3 туры 54 и 108 бросков соответственно.
Победители и призеры личных спортивных соревнований определяются по наименьшей сумме бросков, затраченных на выбивание установленного количества фигур. При равном количестве бросков у нескольких участников, победитель определяется по наименьшему количеству бросков, затраченных на выбивание фигур:
— последнего тура;
— последней партии последнего тура;
— предпоследнего тура;
— «Пулеметное гнездо», «Часовые», «Тир», «Письмо» во всех партиях соревнований.
2. Спортивные соревнования в дисциплине «городки классические — командные соревнования» проводятся по круговой системе. Встречи проводятся из 3-х партий по 15 фигур. Количество очков, начисляемых во встрече: выигрыш — 2, ничья — 1, проигрыш — 0.
Победители и призеры определяются по наибольшей сумме набранных очков. При равенстве суммы очков победитель определяется:
— по сумме очков, набранных в играх между ними;
— по разности выигранных и проигранных партий в играх между ними;
— по лучшему среднему техническому результату в выигранных и ничейных партиях в играх между ними;
— по разности выигранных и проигранных партий во всех играх соревнований;
— по среднему техническому результату в выигранных и ничейных партиях во всех играх соревнований.
3. Спортивные соревнования в дисциплине «городки европейские» проводятся в два этапа.
Первый этап:
Спортсмены играют две партии по 15 фигур 20 бросками каждая, вместо фигуры «Письмо» — фигура «Факс».
Второй этап:
Участники играют по 6 фигур в партии (первая партия — фигуры N 1, 2, 3, 4, 5, «Факс», 2 партия — фигуры N 6, 7, 8, 9, 10, «Факс», третья партия — N 11, 12, 13, 14, 15, «Факс») до двух выигранных партий. В случае ничейного результата после трех партий играется дополнительная партия до первого преимущества на какой-либо фигуре.
4. Спортивные соревнования в дисциплине «городки европейские — командные соревнования» проводятся в два этапа:
Первый этап:
В первом этапе команды играют две партии по 15 фигур 18 бросками каждая, вместо фигуры «Письмо» — фигура «Факс». Первая партия играется, начиная с «Пушки», вторая — с «Факса» в обратном порядке.
Две команды, выбившие большее количество городков на первом этапе разыгрывают 1 и 2 место во втором этапе, команды, показавшие 3 и 4 результаты на первом этапе, разыгрывают 3 место во втором этапе. Места остальных команд определяются по результатам первого этапа.
Второй этап:
Во втором этапе команды играют по 10 фигур в партии (фигуры N 1, 2, 5, 6, 7, 9, 11, 13, 15 и «Факс») до двух выигранных партий. В случае ничейного результата после трех партий играется дополнительная партия до первого преимущества на какой-либо из фигур.
5. Спортивные соревнования в спортивной дисциплине «городки финские — кюккя» проводятся в два этапа:
Первый этап:
В первом этапе участники и участницы играют для выявления четверки сильнейших. Каждый участник или участница играют две партии по 20 городков не более чем 20-ю бросками. Очки за невыбитые городки суммируются со знаком «-» по правилам соревнований, оставшиеся после выбивания всех городков невыполненные броски (из 20 данных бросков) — со знаком «+».
Участники и участницы, набравшие большую сумму, занимают более высокие места. В случае равенства набранных очков места определяются по правилам соревнований.
Второй этап:
Разбивка на пары во втором этапе производится согласно результатам, показанным в первом этапе (1 — 2, 3 — 4). Места спортсменов определяются по результатам, показанным во втором этапе. Соревнования проводятся аналогично первому этапу.
Спортсменам, показавшим в первом этапе соревнований результат +13 — у мужчин и +10 — у женщин, предоставляется право продолжить соревнования в еще одном туре с определением мест с 5-го и далее. Места участников, не уложившихся в установленный норматив, определяются по результатам первого этапа.
6. Спортивные соревнования в спортивной дисциплине «городки финские — кюккя — командные соревнования» проводятся по смешанной системе в два этапа:
Первый этап:
В первом этапе команды играют для выявления четверки сильнейших. Каждая команда играет две партии, в которых выбивает по 20 городков не более чем 24-мя бросками.
Очки за городки, не выбитые 24-мя бросками, суммируются со знаком «-» по правилам соревнований. Очки за невыполненные броски, оставшиеся после выбивания всех 20-ти городков партии (из 24 данных бросков) — со знаком «+». Участники, набравшие большую сумму очков, занимают более высокие места. В случае равенства набранных очков, места определяются по правилам соревнований.
Второй этап:
Команды, занявшие в первом этапе первые четыре места играют в полуфиналах (1 — 4, 2 — 3), а затем, победители — в финале, а проигравшие — в игре за 3 место.
При игре на выбывание команды выполняют одинаковое количество бросков (не более 24). Победитель определяется по наибольшему количеству выбитых городков.
Места участников и команд, не попавших в плей-офф, определяются по результатам первого этапа, с учетом набранных очков и технических результатов. В случае равенства результатов, места определяются по правилам соревнований.
7. Итоговые результаты (протоколы) и отчеты на бумажном и электронном носителях представляются в Минспорт России и ФГБУ «ЦСП» в течение двух недель со дня окончания спортивного соревнования.

filter(element)
def main():
data = DataStorage().get_all_data()
for x in data:
processing(x, 'all', len(data))