[Python, Функциональное программирование] Функциональное ядро на Python

Автор Сообщение
news_bot ®

Стаж: 6 лет 3 месяца
Сообщений: 27286

Создавать темы news_bot ® написал(а)
01-Май-2021 07:30

Главная задача этого поста – показать один мало применяемый в Python подход к работе функциями в рамках более общей функциональной парадигмы, в которой функциями можно манипулировать точно так же, как и любыми другими объектами: присваивать переменным, передавать в качестве аргументов в другие функции, возвращать из функций и включать в последовательности в качестве их элементов.
Конвейер обработки данныхФункциональный стиль программирования очень близок к тому, как размышляет человек во время решения задачи. «Пусть дано x. В целях решения задачи с этими данными необходимо выполнить серию преобразований. Сначала применить к ним f и получить результирующие данные x'. Затем к новым данным применить f2 и получить новые результирующие данные x'' и т.д.Как оказалось, такой образ мыслей отлично укладывается в то, что называется конвейером обработки данных. Конвейер обработки данных состоит из связанных между собой узлов, т.е. функций. Узел характеризуется набором входных и выходных каналов, по которым могут передаваться объекты. Узел ожидает появления определенного набора объектов на своем входном канале, после чего проводит вычисления и порождает объект(ы) на своем выходном канале, которые передаются в следующий узел в конвейере. В функциональных языках конвейеры находят широкое применение, и для их имплементирования даже существуют специальные синтаксические конструкции. Вот как выглядит конвейер в языке F#:    2                                |> ( fun x -> x + 5)             |> ( fun x -> x * x)             |> ( fun x -> x.ToString() ) Здесь входные данные, в данном случае число 2, последовательно обрабатываются серией лямбда-функций. Аналогичный конвейер можно имплементировать на языке Python, но для этого нужно написать специальную функцию, и, разумеется, это будет функция более высокого порядка:# Конвейер обработки данных def pipe(data, *fseq):    for fn in fseq:         data = fn(data)    return dataПриведенный ниже пример демонстрирует работу конвейера:pipe(2,     lambda x: x + 5,     lambda x: x * x,     lambda x: str(x))Число 2 проходит серию преобразований, и в результате будет получено строковое значение '49'. По сравнению с функцией reduce, в которой переданная в качестве аргумента одна единственная редуцирующая функция по очереди применяется к последовательности данных, в функции pipe наоборот последовательность функций применяется к обновляемым данным. Функция pipe получает два аргумента: входные данные data и последовательность функций fseq. Во время первой итерации цикла for данные передаются в первую функцию из последовательности. Эта функция обрабатывает данные и возвращает результат, замещая переменную data новыми данными. Затем эти новые данные отправляются во вторую функцию и т.д. до тех пор, пока не будут выполнены все функции последовательности. По завершению своей работы функция pipe возвращает итоговые данные. Это и есть конвейер обработки данных. Примечание. В приведенном выше примере функции pipe использован оператор упаковки *. В зависимости от контекста оператор * служит для упаковки получаемых нескольких аргументов в одну параметрическую переменную либо распаковки списка передаваемых в функцию аргументов. Когда он используется в параметре функции, как в приведенном выше примере, он служит для упаковки всех аргументов в одну параметрическую переменную. Например,def my_sum(*args): # Упаковка в список    return sum(args) my_sum(1, 2, 3, 4, 5)Когда он используется при вызове функции он служит для разложения передаваемого списка на отдельные аргументы. Например,def fun(a, b, c, d):    print(a, b, c, d) my_list = [1, 2, 3, 4]fun(*my_list) # Разложение на четыре аргументаВ следующих ниже рубриках будут рассмотрены примеры применения конвейера обработки данных на основе функциональной парадигмы программирования.Функциональная имплементация вычисления факториала числаВ приведенном ниже примере показана нерекурсивная версия алгоритма вычисления факториала (factorial) и его рекурсивной версия на основе более эффективной хвостовой рекурсии (factorial_rec). Детали имплементации обеих функций в данном случае не важны. Они приводятся в качестве примеров, на которых будет продемонстрирована работа конвейера обработки данных. Результат выполнения программы показан ниже.1 # Эта программа демонстрирует  2 # функциональную версию функции factorial из главы 12 3  4 def main(): 5     # Конвейер (ядро c нерекурсивным алгоритмом факториала) 6     pipe(int(input('Введите неотрицательное целое число: ')),     7          lambda n: (n, reduce(lambda x, y: x * y, range(1, n + 1))),     8          lambda tup:  9              print(f'Факториал числа {tup[0]} равняется {tup[1]}'))         # Вызвать главную функциюmain()Вывод программы:Введите неотрицательное целое число: 4 (Enter)Факториал числа 4 равняется 24В строке 8 лямбда-функция в последнем узле конвейера получает кортеж, состоящий из введенного пользователем числа и полученного результата. В приведенную ниже расширенную версию программы вычисления факториала добавлена валидация входных данных, и алгоритмы выделены в отдельные функции. Чуть позже будет дано пояснение.1 # Эта программа демонстрирует  2 # функциональную версию функции factorial из главы 12 3 4 def get_int(msg=''): 5     return int(input(msg)) 6 7 def main(): 8     # Алгоритм 1. Рекурсивная версия с хвостовой рекурсией 9     def factorial_rec(n): 10         fn = lambda n, acc=1: acc if n == 0 else fn(n - 1, acc * n)11         return n, fn(n) 12    13     # Алгоритм 2. Нерекурсивная версия14     def factorial(n):     15         return n, reduce(lambda x, y: x * y, range(1, n + 1)) 16    17     # Ввод данных18     def indata():19         def validate(n):  # Валидация входных данных20             if not isinstance(n, int):21                 raise TypeError("Число должно быть целым.")22             if not n >= 0:23                 raise ValueError("Число должно быть >= 0.")24             return n        25         msg = 'Введите неотрицательное целое число: '26         return pipe(get_int(msg), validate)27    28     # Вывод данных29     def outdata():30         def fn(data):31             n, fact = data32             print(f'Факториал числа {n} равняется {fact}') 33         return fn34 35     # Конвейер (функциональное ядро)36     pipe(indata(),     # вход: -       выход: int37          factorial,    # вход: int     выход: кортеж38          outdata())    # вход: кортеж  выход: -    3940 # Вызвать главную функцию41 main()Вывод программы:Введите неотрицательное целое число: 4 (Enter)Факториал числа 4 равняется 24Функциональным ядром программы являются строки 36-38:pipe(indata(),      factorial,      outdata())Они представлены конвейером из трех узлов, т.е. функциями indata, factorial и outdata. Функция indata занимается получением данных от пользователя, которые затем передаются по конвейеру дальше. Функция factorial является собственно обрабатывающим алгоритмом, в данном случае нерекурсивной функцией вычисления факториала, которая получает данные, их обрабатывает и передает по конвейеру дальше. И функция outdata получает данные и показывает их пользователю. Обратите внимание, что функция indata имеет свой собственный конвейер, который состоит из получения данных от пользователя и их валидации. Следует отметить два важных момента. Во-первых, передаваемые от узла к узлу данные должны соответствовать какому-то определенному протоколу. Во-вторых, количество узлов может быть любым.Такая организация программного кода:
  • Позволяет менять узлы конвейера на другие с целью тестирования различных и более эффективных имплементаций алгоритмов. Например, вместо нерекурсивной функции factorial, можно поместить рекурсивную функцию factorial_rec.
pipe(indata(), factorial_rec, outdata())
  • Облегчает проведение отладки программы, позволяя на каждом стыке вставлять отладочный код с целью проверки промежуточных результатов и тестирования производительности отдельных узлов.
Например, рассмотрим вторую возможность – отладку. В этом случае можно написать вспомогательную функцию check:def check(data):    print(data)     return dataИ затем ее вставить в конвейер, чтобы проверить результаты работы отдельных узлов конвейера:pipe(indata(), check, factorial, check, outdata())Если выполнить программу в таком варианте, то будут получены следующие результаты:Вывод программы:Введите неотрицательное целое число: 4 (Enter)4(4, 24)Факториал числа 4 равняется 24Как видно из результатов, на вход в функцию factorial поступает введенное пользователем значение 4, а на выходе из нее возвращается кортеж с исходным числом и полученным результатом (4, 24). Этот результат показывает, что программа работает, как и ожидалось. Как вариант, вместо проверочной функции можно написать функцию-таймер, которая могла бы хронометрировать отдельные узлы конвейера.Приведем еще пару примеров с аналогичной организацией программного кода на основе функционального ядра в виде конвейера.Функциональная имплементация вычисления последовательности Фибоначчи# Эта программа демонстрирует # функциональную версию функции fibonacci из главы 12 def main():    # Алгоритм    def fibonacci(n, x=0, y=1):        # Функция fib возвращает n-ое число последовательности.        fib = lambda n, x=0, y=1: x if n <= 0 else fib(n - 1, y, x + y)        # Функция reduce собирает результаты в список acc        acc = []        reduce(lambda _, y: acc.append(fib(y)), range(n + 1))        return n, acc        # Валидация входных данных    def validate(n):                 if not isinstance(n, int):            raise TypeError("Число должно быть целым.")        if not n >= 0:            raise ValueError("Число должно быть ноль положительным.")        if n > 10:            raise ValueError("Число должно быть не больше 10.")        return n        # Ввод данных    def indata():        msg = 'Введите неотрицательное целое число не больше 10: '        return pipe(get_int(msg), validate)        # Вывод данных    def outdata():        def fn(data):            n, seq = data            msg = f'Первые {n} чисел последовательности Фибоначчи:'            print(msg)             [print(el) for el in seq]        return fn     # Конвейер (функциональное ядро)    pipe(indata(), fibonacci, outdata())  # Вызвать главную функцию.main()Вывод программыВведите неотрицательное целое число не больше 10: 10 (Enter)Первые 10 чисел последовательности Фибоначчи:11235813213455Функциональная имплементация суммирования диапазона значений последовательности# Эта программа демонстрирует # функциональную версию функции range_sum из главы 12 def main():    # Алгоритм    def range_sum(data):          seq, params = data        fn = lambda start, end: 0 if start > end \                                  else seq[start] + fn(start + 1, end)        return fn(*params)     # Ввод данных    def indata():        seq = [1, 2, 3, 4, 5, 6, 7, 8, 9]            params = (2,5)   # params - это параметры start, end        return seq, params          # Вывод данных    def outdata():        def f(data):            msg = 'Сумма значений со 2 по 5 позиции равняется '            print(msg, format(data), sep='')         return f        # Конвейер (функциональное ядро)    pipe(indata(), range_sum, outdata())  # Вызвать главную функцию.main()Вывод программыСумма значений со 2 по 5 позиции равняется 18Приведенный в настоящей главе материал имеет ознакомительный характер и предназначен для того, чтобы продемонстрировать возможности функционального парадигмы программирования на Python с целью дальнейших самостоятельных исследований и побудить программистов дать функциональному стилю шанс. Исходный код поста находится в моем репо на Github.
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_python, #_funktsionalnoe_programmirovanie (Функциональное программирование), #_funktsii (функции), #_funktsionalnoe_programmirovanie (функциональное программирование), #_python, #_konvejer (конвейер), #_python, #_funktsionalnoe_programmirovanie (
Функциональное программирование
)
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 11-Май 15:34
Часовой пояс: UTC + 5