Вступление
Функциональное программирование - это популярная парадигма программирования, тесно связанная с математическими основами информатики. Хотя нет строгого определения того, что представляет собой функциональный язык, мы считаем их языками, которые используют функции для преобразования данных.
Python не является функциональным языком программирования, но он включает в себя некоторые из своих концепций наряду с другими парадигмами программирования. С Python легко написать код в функциональном стиле, который может обеспечить лучшее решение поставленной задачи.
Концепции функционального программирования
Функциональные языки - это декларативные языки, они сообщают компьютеру, какой результат они хотят. Это обычно контрастирует с императивными языками, которые говорят компьютеру, какие шаги нужно предпринять для решения проблемы. Python обычно кодируется императивно, но при необходимости может использовать декларативный стиль.
На некоторые функции Python повлиял чисто функциональный язык программирования Haskell. Чтобы лучше понять, что такое функциональный язык, давайте рассмотрим возможности Haskell, которые можно рассматривать как желательные функциональные черты:
- Чистые функции - не имеют побочных эффектов, то есть не меняют состояние программы. При одном и том же вводе чистая функция всегда будет производить один и тот же вывод.
- Неизменяемость - данные не могут быть изменены после создания.
Возьмем, к примеру, создание
List
из трех элементов и сохранение его в переменнойmy_list
. Еслиmy_list
неизменяемый, вы не сможете изменять отдельные элементы. Вам нужно будет установитьmy_list
в новыйList
если вы хотите использовать другие значения. - Функции высшего порядка - функции могут принимать другие функции в качестве параметров, а функции могут возвращать новые функции в качестве выходных данных. Это позволяет нам абстрагироваться от действий, давая нам гибкость в поведении нашего кода.
Haskell также повлиял на итераторы и генераторы в Python своей отложенной загрузкой, но эта функция не является необходимой для функционального языка.
Функциональное программирование на Python
Без каких-либо специальных функций или библиотек Python мы можем начать кодирование более функциональным способом.
Чистые функции
Если вы хотите, чтобы функции были чистыми, не изменяйте значение ввода или любые данные, которые существуют за пределами области действия функции.
Это значительно упрощает тестирование функции, которую мы пишем. Поскольку он не меняет состояние какой-либо переменной, мы гарантированно получаем один и тот же результат каждый раз, когда запускаем функцию с одним и тем же входом.
Давайте создадим чистую функцию для умножения чисел на 2:
def multiply_2_pure(numbers):
new_numbers = []
for n in numbers:
new_numbers.append(n * 2)
return new_numbers
original_numbers = [1, 3, 5, 10]
changed_numbers = multiply_2_pure(original_numbers)
print(original_numbers) # [1, 3, 5, 10]
print(changed_numbers) # [2, 6, 10, 20]
Исходный список numbers
не изменился, и мы не ссылаемся на какие-либо
другие переменные вне функции, поэтому он чистый.
Неизменность
У вас когда-нибудь была ошибка, когда вы задавались вопросом, как
переменная, установленная вами на 25, стала None
? Если бы эта
переменная была неизменной, ошибка возникла бы там, где переменная была
изменена, а не там, где измененное значение уже повлияло на программное
обеспечение - основную причину ошибки можно найти ранее.
Python предлагает несколько неизменяемых типов данных, популярным из
которых является Tuple
. Давайте сравним кортеж со
списком , который является изменяемым:
mutable_collection = ['Tim', 10, [4, 5]]
immutable_collection = ('Tim', 10, [4, 5])
# Reading from data types are essentially the same:
print(mutable_collection[2]) # [4, 5]
print(immutable_collection[2]) # [4, 5]
# Let's change the 2nd value from 10 to 15
mutable_collection[1] = 15
# This fails with the tuple
immutable_collection[1] = 15
Вы увидите TypeError: 'tuple' object does not support item assignment
.
Теперь есть интересный сценарий, когда Tuple
может показаться
изменяемым объектом. Например, если мы хотим изменить список в
immutable_collection
с [4, 5]
на [4, 5, 6]
, вы можете сделать
следующее:
immutable_collection[2].append(6)
print(immutable_collection[2]) # [4, 5, 6]
Это работает, потому что List
является изменяемым объектом. Попробуем
изменить список обратно на [4, 5]
.
immutable_collection[2] = [4, 5]
# This throws a familiar error:
# TypeError: 'tuple' object does not support item assignment
Он терпит неудачу, как мы и ожидали. Хотя мы можем изменить содержимое
изменяемого объекта в Tuple
, мы не можем изменить ссылку на
изменяемый объект, который хранится в памяти.
Функции высшего порядка
Напомним, что функции высшего порядка либо принимают функцию в качестве аргумента, либо возвращают функцию для дальнейшей обработки. Давайте проиллюстрируем, как просто и то, и другое можно создать в Python.
Рассмотрим функцию, которая печатает строку несколько раз:
def write_repeat(message, n):
for i in range(n):
print(message)
write_repeat('Hello', 5)
Что, если бы мы хотели 5 раз записать в файл или 5 раз записать сообщение? Вместо того, чтобы писать 3 разные функции, которые все циклически повторяются, мы можем написать 1 функцию высшего порядка, которая принимает эти функции в качестве аргумента:
def hof_write_repeat(message, n, action):
for i in range(n):
action(message)
hof_write_repeat('Hello', 5, print)
# Import the logging library
import logging
# Log the output as an error instead
hof_write_repeat('Hello', 5, logging.error)
Теперь представьте, что нам поручено создать функции, увеличивающие числа в списке на 2, 5 и 10. Начнем с первого случая:
def add2(numbers):
new_numbers = []
for n in numbers:
new_numbers.append(n + 2)
return new_numbers
print(add2([23, 88])) # [25, 90]
Хотя писать функции add5
и add10
, очевидно, что они будут работать
одинаково: перебирать список и добавлять инкрементатор. Поэтому вместо
того, чтобы создавать множество различных функций приращения, мы создаем
1 функцию высшего порядка:
def hof_add(increment):
# Create a function that loops and adds the increment
def add_increment(numbers):
new_numbers = []
for n in numbers:
new_numbers.append(n + increment)
return new_numbers
# We return the function as we do any other value
return add_increment
add5 = hof_add(5)
print(add5([23, 88])) # [28, 93]
add10 = hof_add(10)
print(add10([23, 88])) # [33, 98]
Функции высшего порядка придают нашему коду гибкость. Абстрагируя, какие функции применяются или возвращаются, мы получаем больший контроль над поведением нашей программы.
Python предоставляет несколько полезных встроенных функций высшего порядка, которые значительно упрощают работу с последовательностями. Сначала мы рассмотрим лямбда-выражения, чтобы лучше использовать эти встроенные функции.
Лямбда-выражения
Лямбда-выражение - это анонимная функция. Когда мы создаем функции в
Python, мы используем def
и даем ему имя. Лямбда-выражения позволяют
нам определять функцию намного быстрее.
Давайте создадим функцию высшего порядка hof_product
которая
возвращает функцию, которая умножает число на предопределенное значение:
def hof_product(multiplier):
return lambda x: x * multiplier
mult6 = hof_product(6)
print(mult6(6)) # 36
Лямбда-выражение начинается с ключевого слова lambda
за которым
следуют аргументы функции. После двоеточия следует код, возвращаемый
лямбдой. Эта возможность создавать функции «на ходу» активно
используется при работе с функциями высшего порядка.
Если вам нужна дополнительная информация, мы рассмотрим гораздо больше лямбда-выражений, которые мы рассмотрим в нашей статье « Лямбда-функции в Python».
Встроенные функции высшего порядка
Python реализовал некоторые часто используемые функции высшего порядка
из языков функционального программирования, что значительно упрощает
обработку повторяемых объектов, таких как списки и итераторы. По
причинам эффективности использования пространства / памяти эти функции
возвращают iterator
вместо списка.
карта
Функция map
позволяет нам применять функцию к каждому элементу в
повторяемом объекте. Например, если у нас есть список имен и мы хотим
добавить приветствие к строкам, мы можем сделать следующее:
names = ['Shivani', 'Jason', 'Yusef', 'Sakura']
greeted_names = map(lambda x: 'Hi ' + x, names)
# This prints something similar to: <map object at 0x10ed93cc0>
print(greeted_names)
# Recall, that map returns an iterator
# We can print all names in a for loop
for name in greeted_names:
print(name)
Фильтр
Функция filter
проверяет каждый элемент в итеративном объекте с
помощью функции, которая возвращает либо True
либо False
, сохраняя
только те True
. Если бы у нас был список чисел и мы хотели бы
сохранить те, которые делятся на 5, мы можем сделать следующее:
numbers = [13, 4, 18, 35]
div_by_5 = filter(lambda num: num % 5 == 0, numbers)
# We can convert the iterator into a list
print(list(div_by_5)) # [35]
Объединение map
и filter
Поскольку каждая функция возвращает итератор, и обе они принимают итерируемые объекты, мы можем использовать их вместе для некоторых действительно выразительных манипуляций с данными!
# Let's arbitrarily get the all numbers divisible by 3 between 1 and 20 and cube them
arbitrary_numbers = map(lambda num: num ** 3, filter(lambda num: num % 3 == 0, range(1, 21)))
print(list(arbitrary_numbers)) # [27, 216, 729, 1728, 3375, 5832]
Выражение в arbitrary_numbers
можно разбить на 3 части:
range(1, 21)
- это повторяемый объект, представляющий числа от 1, 2, 3, 4 ... 19, 20.filter(lambda num: num % 3 == 0, range(1, 21))
- итератор для числовой последовательности 3, 6, 9, 12, 15 и 18.- Когда они построены в кубе
map
мы можем получить итератор для числовой последовательности 27, 216, 729, 1728, 3375 и 5832.
Составить список
Популярная функция Python, которая занимает видное место в языках
функционального программирования, - это составление списков. Подобно
map
и filter
, списки позволяют изменять данные в сжатой и
выразительной форме.
Давайте вместо этого попробуем наши предыдущие примеры с map
и
filter
со списком:
# Recall
names = ['Shivani', 'Jan', 'Yusef', 'Sakura']
# Instead of: map(lambda x: 'Hi ' + x, names), we can do
greeted_names = ['Hi ' + name for name in names]
print(greeted_names) # ['Hi Shivani', 'Hi Jason', 'Hi Yusef', 'Hi Sakura']
Базовое понимание списка следует этому формату: [результат for
единственного-элемента in
списка].
Если мы хотим отфильтровать объекты, нам нужно использовать ключевое
слово if
# Recall
numbers = [13, 4, 18, 35]
# Instead of: filter(lambda num: num % 5 == 0, numbers), we can do
div_by_5 = [num for num in numbers if num % 5 == 0]
print(div_by_5) # [35]
# We can manage the combined case as well:
# Instead of:
# map(lambda num: num ** 3, filter(lambda num: num % 3 == 0, range(1, 21)))
arbitrary_numbers = [num ** 3 for num in range(1, 21) if num % 3 == 0]
print(arbitrary_numbers) # [27, 216, 729, 1728, 3375, 5832]
Каждое map
и filter
может быть выражено в виде списка.
Некоторые моменты, которые следует учитывать
Хорошо известно, что создатель Python, Гвидо ван Россум, не планировал, чтобы Python имел функциональные возможности, но оценил некоторые преимущества, которые его введение принесло языку. Он обсудил историю возможностей языка функционального программирования в одном из своих сообщений в блоге . В результате реализации языка не были оптимизированы для функций функционального программирования.
Более того, сообщество разработчиков Python не поощряет использование
огромного количества функций функционального программирования. Если бы
вы писали код для рассмотрения глобальным сообществом Python, вы бы
написали понимание списков вместо использования map
или filter
.
Лямбды будут использоваться минимально, как вы назвали бы свои функции.
В интерпретаторе Python введите import this
и вы увидите «Дзен
Python». Python обычно поощряет писать код наиболее очевидным способом.
В идеале весь код должен быть написан одним способом - сообщество не
считает, что он должен быть в функциональном стиле.
Заключение
Функциональное программирование - это парадигма программирования, в которой программное обеспечение в основном состоит из функций, обрабатывающих данные на протяжении всего своего выполнения. Хотя нет единого определения того, что такое функциональное программирование, мы смогли изучить некоторые характерные особенности функциональных языков: чистые функции, неизменяемость и функции высшего порядка.
Python позволяет нам писать код в функциональном декларативном стиле. Он
даже поддерживает многие общие функциональные возможности, такие как
лямбда-выражения, а также функции map
и filter
.
Однако сообщество Python не всегда рассматривает использование методов функционального программирования передовой практикой. Тем не менее, мы узнали новые способы решения проблем, и при необходимости можем решать проблемы, используя выразительность функционального программирования.