Содержание

Введение в функциональное программирование на Python / Хабр

Рассуждая о функциональном программировании, люди часто начинают выдавать кучу «функциональных» характеристик. Неизменяемые данные, функции первого класса и оптимизация хвостовой рекурсии. Это свойства языка, помогающие писать функциональные программы. Они упоминают мапирование, каррирование и использование функций высшего порядка. Это приёмы программирования, использующиеся для написания функционального кода. Они упоминают распараллеливание, ленивые вычисления и детерменизм. Это преимущества функциональных программ.

Забейте. Функциональный код отличается одним свойством: отсутствием побочных эффектов. Он не полагается на данные вне текущей функции, и не меняет данные, находящиеся вне функции. Все остальные «свойства» можно вывести из этого.

Нефункциональная функция:

a = 0
def increment1():
    global a
    a += 1

Функциональная функция:

def increment2(a):
    return a + 1

Вместо проходов по списку используйте map и reduce


Map

Принимает функцию и набор данных. Создаёт новую коллекцию, выполняет функцию на каждой позиции данных и добавляет возвращаемое значение в новую коллекцию. Возвращает новую коллекцию.

Простой map, принимающий список имён и возвращающий список длин:

name_lengths = map(len, ['Маша', 'Петя', 'Вася'])
print name_lengths
# => [4, 4, 3]

Этот map возводит в квадрат каждый элемент:

squares = map(lambda x: x * x, [0, 1, 2, 3, 4])
print squares
# => [0, 1, 4, 9, 16]

Он не принимает именованную функцию, а берёт анонимную, определённую через lambda. Параметры lambda определены слева от двоеточия. Тело функции – справа. Результат возвращается неявным образом.

Нефункциональный код в следующем примере принимает список имён и заменяет их случайными прозвищами.

import random
names = ['Маша', 'Петя', 'Вася']
code_names = ['Шпунтик', 'Винтик', 'Фунтик']
for i in range(len(names)):
    names[i] = random.choice(code_names)
print names
# => ['Шпунтик', 'Винтик', 'Шпунтик']

Алгоритм может присвоить одинаковые прозвища разным секретным агентам. Будем надеяться, что это не послужит источником проблем во время секретной миссии.

Перепишем это через map:

import random
names = ['Маша', 'Петя', 'Вася']
secret_names = map(lambda x: random.choice(['Шпунтик', 'Винтик', 'Фунтик']), names)

Упражнение 1. Попробуйте переписать следующий код через map. Он принимает список реальных имён и заменяет их прозвищами, используя более надёжный метод.

names = ['Маша', 'Петя', 'Вася']
for i in range(len(names)):
    names[i] = hash(names[i])
print names
# => [6306819796133686941, 8135353348168144921, -1228887169324443034]

Моё решение:

names = ['Маша', 'Петя', 'Вася']
secret_names = map(hash, names)

Reduce

Reduce принимает функцию и набор пунктов. Возвращает значение, получаемое комбинированием всех пунктов.

Пример простого reduce. Возвращает сумму всех пунктов в наборе:

sum = reduce(lambda a, x: a + x, [0, 1, 2, 3, 4])
print sum
# => 10

x – текущий пункт, а – аккумулятор. Это значение, которое возвращает выполнение lambda на предыдущем пункте. reduce() перебирает все значения, и запускает для каждого lambda на текущих значениях а и х, и возвращает результат в а для следующей итерации.

А чему равно а в первой итерации? Оно равно первому элементу коллекции, и reduce() начинает работать со второго элемента. То есть, первый х будет равен второму предмету набора.

Следующий пример считает, как часто слово «капитан» встречается в списке строк:

sentences = ['капитан джек воробей',
             'капитан дальнего плавания',
             'ваша лодка готова, капитан']
cap_count = 0
for sentence in sentences:
    cap_count += sentence.count('капитан')
print cap_count
# => 3

Тот же код с использованием reduce:

sentences = ['капитан джек воробей',
             'капитан дальнего плавания',
             'ваша лодка готова, капитан']
cap_count = reduce(lambda a, x: a + x.count('капитан'),
                   sentences,
                   0)

А откуда здесь берётся начальное значение а? Оно не может быть вычислено из количества повторений в первой строке.

Поэтому оно задаётся как третий аргумент функции reduce().

Почему map и reduce лучше?

Во-первых, они обычно укладываются в одну строку.

Во-вторых, важные части итерации,– коллекция, операция и возвращаемое значение,– всегда находятся в одном месте map и reduce.

В-третьих, код в цикле может изменить значение ранее определённых переменных, или влиять на код, находящийся после него. По соглашению, map и reduce – функциональны.

В-четвёртых, map и reduce – элементарные операции. Вместо построчного чтения циклов читателю проще воспринимать map и reduce, встроенные в сложные алгоритмы.

В-пятых, у них есть много друзей, позволяющих полезное, слегка изменённое поведение этих функций. Например, filter, all, any и find.

Упражнение 2: перепишите следующий код, используя map, reduce и filter. Filter принимает функцию и коллекцию. Возвращает коллекцию тех вещей, для которых функция возвращает True.

people = [{'имя': 'Маша', 'рост': 160},
    {' рост ': 'Саша', ' рост ': 80},
    {'name': 'Паша'}]
height_total = 0
height_count = 0
for person in people:
    if 'рост' in person:
        height_total += person[' рост ']
        height_count += 1
if height_count > 0:
    average_height = height_total / height_count
    print average_height
    # => 120

Моё решение:

people = [{'имя': 'Маша', 'рост': 160},
    {' рост ': 'Саша', ' рост ': 80},
    {'name': 'Паша'}]
heights = map(lambda x: x['рост'],
              filter(lambda x: 'рост' in x, people))
if len(heights) > 0:
    from operator import add
    average_height = reduce(add, heights) / len(heights)

Пишите декларативно, а не императивно

Следующая программа эмулирует гонку трёх автомобилей.

В каждый момент времени машина либо двигается вперёд, либо нет. Каждый раз программа выводит пройденный автомобилями путь. Через пять промежутков времени гонка заканчивается.

Примеры вывода:

 -
 - -
 - -
 - -
 - -
 - - -
 - - -
 - -
 - - -
 - - - -
 - - -
 - - - -
 - - - -
 - - - -
 - - - - -

Текст программы:

from random import random
time = 5
car_positions = [1, 1, 1]
while time:
    # decrease time
    time -= 1
    print ''
    for i in range(len(car_positions)):
        # move car
        if random() > 0.3:
            car_positions[i] += 1
        # draw car
        print '-' * car_positions[i]

Код императивен. Функциональная версия была бы декларативной – она бы описывала, что нужно сделать, а не то, как это надо сделать.

Используем функции

Декларативности можно достичь, вставляя код в функции:

from random import random
def move_cars():
    for i, _ in enumerate(car_positions):
        if random() > 0. 3:
            car_positions[i] += 1
def draw_car(car_position):
    print '-' * car_position
def run_step_of_race():
    global time
    time -= 1
    move_cars()
def draw():
    print ''
    for car_position in car_positions:
        draw_car(car_position)
time = 5
car_positions = [1, 1, 1]
while time:
    run_step_of_race()
    draw()

Для понимания программы читатель просматривает основной цикл. «Если осталось время, пройдём один шаг гонки и выведем результат. Снова проверим время». Если читателю надо будет разобраться, как работает шаг гонки, он сможет прочесть его код отдельно.

Комментарии не нужны, код объясняет сам себя.

Разбиение кода на функции делает код более читаемым. Этот приём использует функции, но лишь как подпрограммы. Они упаковывают код, но не делают его функциональным. Функции влияют на окружающий их код и меняют глобальные переменные, а не возвращают значения. Если читатель встречает переменную, ему потребуется найти, откуда она взялась.

Вот функциональная версия этой программы:

from random import random
def move_cars(car_positions):
    return map(lambda x: x + 1 if random() > 0. 3 else x,
               car_positions)
def output_car(car_position):
    return '-' * car_position
def run_step_of_race(state):
    return {'time': state['time'] - 1,
            'car_positions': move_cars(state['car_positions'])}
def draw(state):
    print ''
    print '\n'.join(map(output_car, state['car_positions']))
def race(state):
    draw(state)
    if state['time']:
        race(run_step_of_race(state))
race({'time': 5,
      'car_positions': [1, 1, 1]})

Теперь код разбит на функциональные функции. Тому есть три признака. Первый – нет расшаренных переменных. time и car_positions передаются прямиком в race(). Второе – функции принимают параметры. Третье – переменные не меняются внутри функций, все значения возвращаются. Каждый раз, когда run_step_of_race() проделывает следующий шаг, он передаётся опять в следующий.

Вот вам две функции zero() и one():

def zero(s):
    if s[0] == "0":
        return s[1:]
def one(s):
    if s[0] == "1":
        return s[1:]

zero() принимает строку s. Если первый символ – 0, то возвращает остаток строки. Если нет – тогда None. one() делает то же самое, если первый символ – 1.

Представим функцию rule_sequence(). Она принимает строку и список из функций-правил, состоящий из функций zero и one. Она вызывает первое правило, передавая ему строку. Если не возвращено None, то берёт возвращённое значение и вызывает следующее правило. И так далее. Если возвращается None, rule_sequence() останавливается и возвращает None. Иначе – значение последнего правила.

Примеры входных и выходных данных:

print rule_sequence('0101', [zero, one, zero])
# => 1
print rule_sequence('0101', [zero, zero])
# => None

Императивная версия rule_sequence():

def rule_sequence(s, rules):
    for rule in rules:
        s = rule(s)
        if s == None:
            break
    return s

Упражнение 3. Этот код использует цикл. Перепишите его в декларативном виде с использованием рекурсии.

Моё решение:

def rule_sequence(s, rules):
    if s == None or not rules:
        return s
    else:
        return rule_sequence(rules[0](s), rules[1:])

Используйте конвейеры (pipelines)

Теперь перепишем другой вид циклов при помощи приёма под названием конвейер.

Следующий цикл изменяет словари, содержащие имя, неправильную страну происхождения и статус некоторых групп.

bands = [{'name': 'sunset rubdown', 'country': 'UK', 'active': False},
         {'name': 'women', 'country': 'Germany', 'active': False},
         {'name': 'a silver mt. zion', 'country': 'Spain', 'active': True}]
def format_bands(bands):
    for band in bands:
        band['country'] = 'Canada'
        band['name'] = band['name'].replace('.', '')
        band['name'] = band['name'].title()
format_bands(bands)
print bands
# => [{'name': 'Sunset Rubdown', 'active': False, 'country': 'Canada'},
#     {'name': 'Women', 'active': False, 'country': 'Canada' },
#     {'name': 'A Silver Mt Zion', 'active': True, 'country': 'Canada'}]

Название функции «format» слишком общее. И вообще, код вызывает некоторое беспокойство. В одном цикле происходят три разные вещи. Значение ключа ‘country’ меняется на ‘Canada’. Убираются точки и первая буква имени меняется на заглавную. Сложно понять, что код должен делать, и сложно сказать, делает ли он это. Его тяжело использовать, тестировать и распараллеливать.

Сравните:

print pipeline_each(bands, [set_canada_as_country,
                            strip_punctuation_from_name,
                            capitalize_names])

Всё просто. Вспомогательные функции выглядят функциональными, потому что они связаны в цепочку. Выход предыдущей – вход следующей. Их просто проверить, использовать повторно, проверять и распараллеливать.

pipeline_each() перебирает группы по одной, и передаёт их функциям преобразования, вроде set_canada_as_country(). После применения функции ко всем группам, pipeline_each() делает из них список и передаёт следующей.

Посмотрим на функции преобразования.

def assoc(_d, key, value):
    from copy import deepcopy
    d = deepcopy(_d)
    d[key] = value
    return d
def set_canada_as_country(band):
    return assoc(band, 'country', "Canada")
def strip_punctuation_from_name(band):
    return assoc(band, 'name', band['name']. replace('.', ''))
def capitalize_names(band):
    return assoc(band, 'name', band['name'].title())

Каждая связывает ключ группы с новым значением. Без изменения оригинальных данных это тяжело сделать, поэтому мы решаем это с помощью assoc(). Она использует deepcopy() для создания копии переданного словаря. Каждая функция преобразовывает копию и возвращает эту копию.

Всё вроде как нормально. Оригиналы данных защищены от изменений. Но в коде есть два потенциальных места для изменений данных. В strip_punctuation_from_name() создаётся имя без точек через вызов calling replace() с оригинальным именем. В capitalize_names() создаётся имя с первой прописной буквой на основе title() и оригинального имени. Если replace и time не функциональны, то и strip_punctuation_from_name() с capitalize_names() не функциональны.

К счастью, они функциональны. В Python строки неизменяемы. Эти функции работают с копиями строк. Уфф, слава богу.

Такой контраст между строками и словарями (их изменяемостью) в Python демонстрирует преимущества языков типа Clojure. Там программисту не надо думать, не изменит ли он данные. Не изменит.

Упражнение 4. Попробуйте сделать функцию pipeline_each. Задумайтесь над последовательностью операций. Группы – в массиве, передаются по одной для первой функции преобразования. Затем полученный массив передаётся по одной штучке для второй функции, и так далее.

Моё решение:

def pipeline_each(data, fns):
    return reduce(lambda a, x: map(x, a),
                  fns,
                  data)

Все три функции преобразования в результате меняют конкретное поле у группы. call() можно использовать, чтобы создать абстракцию для этого. Она принимает функцию и ключ, к которому она будет применена.

set_canada_as_country = call(lambda x: 'Canada', 'country')
strip_punctuation_from_name = call(lambda x: x.replace('.', ''), 'name')
capitalize_names = call(str.title, 'name')
print pipeline_each(bands, [set_canada_as_country,
                            strip_punctuation_from_name,
                            capitalize_names])

Или, жертвуя читаемостью:

print pipeline_each(bands, [call(lambda x: 'Canada', 'country'),
                            call(lambda x: x. replace('.', ''), 'name'),
                            call(str.title, 'name')])

Код для call():

def assoc(_d, key, value):
    from copy import deepcopy
    d = deepcopy(_d)
    d[key] = value
    return d
def call(fn, key):
    def apply_fn(record):
        return assoc(record, key, fn(record.get(key)))
    return apply_fn

Что тут у нас происходит.

Один. call – функция высшего порядка, т.к. принимает другую функцию как аргумент и возвращает функцию.

Два. apply_fn() похожа на функции преобразования. Получает запись (группу). Ищет значение record[key]. Вызывает fn. Присваивает результат в копию записи и возвращает её.

Три. call сам ничего не делает. Всю работу делает apply_fn(). В примере использования pipeline_each(), один экземпляр apply_fn() задаёт ‘country’ значение ‘Canada’. Другой – делает первую букву прописной.

Четыре. При выполнении экземпляра apply_fn() функции fn и key не будут доступны в области видимости. Это не аргументы apply_fn() и не локальные переменные. Но доступ к ним будет. При определении функции она сохраняет ссылки на переменные, которые она замыкает – те, что были определены снаружи функции, и используются внутри. При запуске функции переменные ищутся среди локальных, затем среди аргументов, а затем среди ссылок на замкнутые. Там и найдутся fn и key.

Пять. В call нет упоминания групп. Это оттого, что call можно использовать для создания любых конвейеров, независимо от их содержимого. Функциональное программирование, в частности, строит библиотеку общих функций, пригодных для композиций и для повторного использования.

Молодцом. Замыкания, функции высшего порядка и область видимости – всё в нескольких параграфах. Можно и чайку с печеньками выпить.

Остаётся ещё одна обработка данных групп. Убрать всё, кроме имени и страны. Функция extract_name_and_country():

def extract_name_and_country(band):
    plucked_band = {}
    plucked_band['name'] = band['name']
    plucked_band['country'] = band['country']
    return plucked_band
print pipeline_each(bands, [call(lambda x: 'Canada', 'country'),
                            call(lambda x: x. replace('.', ''), 'name'),
                            call(str.title, 'name'),
                            extract_name_and_country])
# => [{'name': 'Sunset Rubdown', 'country': 'Canada'},
#     {'name': 'Women', 'country': 'Canada'},
#     {'name': 'A Silver Mt Zion', 'country': 'Canada'}]

extract_name_and_country() можно было бы написать в обобщённом виде под названием pluck(). Использовалась бы она так:

print pipeline_each(bands, [call(lambda x: 'Canada', 'country'),
                            call(lambda x: x.replace('.', ''), 'name'),
                            call(str.title, 'name'),
                            pluck(['name', 'country'])])

Упражнение 5. pluck принимает список ключей, которые надо извлечь из записей. Попробуйте её написать. Это буде функция высшего порядка.

Моё решение:

def pluck(keys):
    def pluck_fn(record):
        return reduce(lambda a, x: assoc(a, x, record[x]),
                      keys,
                      {})
    return pluck_fn

И что теперь?

Функциональный код хорошо сочетается с традиционным. Преобразования из этой статьи можно использовать в любом языке. Попробуйте и вы для своего кода.

Вспомните про Машу, Петю и Васю. Превратите итерации по спискам в map и reduces.

Вспомните гонки. Разбейте код на функции, и сделайте их функциональными. Превратите цикл в рекурсию.

Вспомните про группы. Превратите последовательность операций в конвейер.

Функциональное программирование на Python. Часть 1

Функциональное программирование является интересной парадигмой, которая поможет по-новому взглянуть на процесс разработки и, несомненно, расширит ваш опыт новыми идеями и подходами.

  • Парадигмы программирования
  • В чем суть функционального программирования?
  • Примеры работы в функциональном стиле
    • Функции, как объекты первого класса
    • Рекурсия
    • Функции высшего порядка
    • Чистые функции

Парадигмы программирования

Для начала обратимся к википедии за определением понятия “парадигма программирования”. Парадигма программирования — это совокупность идей и понятий, определяющих стиль написания компьютерных программ (подход к программированию). Это способ концептуализации, определяющий организацию вычислений и структурирование работы, выполняемой компьютером.

Выделяют две крупные парадигмы программирования: императивная и декларативная.

Императивное программирование предполагает ответ на вопрос “Как?”. В рамках этой парадигмы вы задаете последовательность действий, которые нужно выполнить, для того чтобы получить результат. Результат выполнения сохраняется в ячейках памяти, к которым можно обратиться в последствии. 

Декларативное программирование предполагает ответ на вопрос “Что?”. Здесь вы описываете задачу, даете спецификацию, говорите, что вы хотите получить в результате выполнения программы, но не определяете, как этот ответ будет получен.

Каждая из этих парадигм включает в себя более специфические модели. В промышленности наибольшее распространение получили структурное и объектно-ориентированное программирование из группы “императивное программирование” и функциональное программирование из группы “декларативное программирование”.

В рамках структурного подхода к программированию основное внимание сосредоточено на декомпозиции – разбиении программы/задачи на отдельные блоки / подзадачи. Разработка ведётся пошагово, методом “сверху вниз”. Наиболее распространенным языком, который предполагает использование структурного подхода к программирования является язык C, в нем, основными строительными блоками являются функции.

В рамках объектно-ориентированного (ООП) подхода программа представляется в виде совокупности объектов, каждый из которых является экземпляром определенного класса, классы образуют иерархию наследования. ООП базируется на следующих принципах: инкапсуляция, наследование, полиморфизм, абстракция. Примерами языков, которые позволяют вести разработку в этой парадигме являются C#, Java.

В рамках функционального программирования выполнение программы – это процесс вычисления, который трактуется как вычисление значений функций в математическом понимании последних (в отличие от функций как подпрограмм в процедурном программировании). Языки, которые реализуют эту парадигму – это Haskell, Lisp.

Довольно часто бывает так, что дизайн языка позволяет использовать все перечисленные парадигмы (например Python) или только часть из них (например, C++).

В чем суть функционального программирования?

Языки, которые можно отнести в функциональной парадигме обладают определенным набором свойств. Если язык не является чисто функциональным, но реализует эти свойства, то на нем можно разрабатывать, как говорят, в функциональном стиле. Свойства функционального стиля программирования:

  • Функции являются объектами первого класса (First Class Object). Это означает, что с функциями вы можете работать, также как и с данными: передавать их в качестве аргументов другим функциям, присваивать переменным и т.п.
  • В функциональных языках не используются переменные (как именованные ячейки памяти), т.к. там нет состояний, а т.к. нет переменных, то и нет операции присваивания, как это понимается в императивном программировании.
  • Рекурсия является основным подходом для управления вычислениями, а не циклы и условные операторы.
  • Используются функции высшего порядка (High Order Functions). Функции высшего порядка – это функций, которые могут в качестве аргументов принимать другие функции.
  • Функции являются “чистыми” (Pure Functions) – т.е. не имеют побочных эффектов (иногда говорят: не имеют сайд-эффектов).
  • Акцент на том, что должно быть вычислено, а не на том, как вычислять.
  • Фокус на работе с контейнерами (хотя это спорное положение).

Python не является функциональным языком программирования, но его возможностей хватает, чтобы разрабатывать программы в функциональном стиле. 

Примеры работы в функциональном стиле

Функции, как объекты первого класса

Ниже представлен код, который демонстрирует возможность Python, в части того, что функцию можно присвоить переменной и использовать ее:

>>> def mul5(value):
        return value*5
>>> v1 = 3
>>> f1 = mul5
>>> f1(v1)
15

Переменной f1, в качестве значения, присваивается функция mul5, после этого появляется возможность вызвать ее как функцию.

Рекурсия

Рекурсия – это вызов функции из нее же самой. Для начала приведем пример не рекурсивной функции, которая считает факториал:

def fact_iter(n):
   if n == 0 or n == 1:
       return 1
   else:
       prod = 1
       for i in range(1, n + 1):
           prod *= i
       return prod
print(fact_iter(5))

В качестве результата получим число 120.

Перепишем ее через рекурсию:

def fact_rec(n):
   if n == 0 or n == 1:
       return 1
   else:
       return n * fact_rec(n - 1)
print(fact_rec(5))

Результат получим аналогичный первому. Заметим, что такая реализация не является эффективной по памяти, о том почему это так, и как сделать правильно см “О рекурсии и итерации“

Функции высшего порядка

Функции высшего порядка принимают в качестве аргументов другие функции. В стандартную библиотеку Python входит достаточно много таких функции, в качестве примера приведем функцию map. Она принимает функцию и список, применяет функцию к каждому элементу списка и возвращает новый модифицированный список.

>>> fn = lambda x: x**2
>>> print(list(map(fn, [1, 2, 3, 4, 5])))
[1, 4, 9, 16, 25]

Чистые функции

Чистые функции – это функции, которые не имеют побочных эффектов. В Python это не выполняется. Необходимо самостоятельно следить за тем, чтобы функция была чистой.

Например, следующая функция модифицирует данные, которые в нее передаются:

def fun_not_clear(data):
    if len(data) > 0:
        data[0] += 10
    return data*2
d1 = [1, 2, 3]
d2 = fun_not_clear(d1)
>>> print(d1)
[11, 2, 3]
>>> print(d2)
[11, 2, 3, 11, 2, 3]

В функцию fun_not_clear был передан массив данных, над которым был проведен ряд модификаций и сформирован новый массив. При этом исходный массив тоже был изменен. Это пример побочного эффекта, который можно получить в Python. Более корректный вариант будет выглядеть так:

def fun_clear(data):
    data = data[:]
    if len(data) > 0:
        data[0] += 10
    return data*2
d1 = [1, 2, 3]
d2 = fun_clear(d1)
>>> print(d1)
[1, 2, 3]
>>> print(d2)
[11, 2, 3, 11, 2, 3]

В следующих статьях будут более подробно описаны аспекты функционального программирования, которые можно использовать в Python. Также большое внимание уделим библиотекам, которые предоставляют инструменты, позволяющие более комфортно работать в этой парадигме.

P.S.

Вводные уроки по “Линейной алгебре на Python” вы можете найти соответствующей странице нашего сайта. Все уроки по этой теме собраны в книге “Линейная алгебра на Python”.

Если вам интересна тема анализа данных, то мы рекомендуем ознакомиться с библиотекой Pandas.  Для начала вы можете познакомиться с вводными уроками. Все уроки по библиотеке Pandas собраны в книге “Pandas. Работа с данными”.

Функциональное программирование на Python — GeeksforGeeks

Функциональное программирование — это парадигма программирования, в которой мы пытаемся связать все в стиле чисто математических функций. Это декларативный тип стиля программирования. Его основное внимание уделяется «, что решить », в отличие от императивного стиля, где основное внимание уделяется «, как решить ». Он использует выражения вместо операторов. Выражение оценивается для получения значения, тогда как оператор выполняется для присвоения переменных.

Концепции функционального программирования

Ожидается, что любой язык функционального программирования будет следовать этим концепциям.

  • Чистые функции: Эти функции имеют два основных свойства. Во-первых, они всегда выдают один и тот же результат для одних и тех же аргументов независимо от всего остального. Во-вторых, у них нет побочных эффектов, т. е. они изменяют любой аргумент или глобальные переменные или что-то выводят.
  • Рекурсия: В функциональных языках нет циклов for или while. Итерация в функциональных языках реализуется через рекурсию.
  • Функции первого класса и могут быть высшего порядка: Функции первого класса рассматриваются как переменные первого класса. Переменные первого класса могут быть переданы функциям в качестве параметра, могут быть возвращены из функций или сохранены в структурах данных.
  • Переменные неизменяемы: В функциональном программировании мы не можем изменить переменную после ее инициализации. Мы можем создавать новые переменные, но не можем изменять существующие переменные.

Функциональное программирование на Python

Python также поддерживает парадигмы функционального программирования без поддержки каких-либо специальных функций или библиотек.

Чистые функции

Как обсуждалось выше, чистые функции обладают двумя свойствами.

  • Всегда выдает один и тот же результат для одних и тех же аргументов. Например, 3+7 всегда будет 10, несмотря ни на что.
  • Не изменяет и не модифицирует входную переменную.

Второе свойство также известно как неизменность. Единственным результатом Чистой функции является возвращаемое ею значение. Они детерминированы. Программы, созданные с использованием функционального программирования, легко отлаживать, поскольку чистые функции не имеют побочных эффектов или скрытых операций ввода-вывода. Чистые функции также упрощают написание параллельных/параллельных приложений. Когда код написан в таком стиле, умный компилятор может делать многое — он может распараллеливать инструкции, ожидать оценки результатов, когда они нужны, и запоминать результаты, поскольку результаты никогда не меняются, пока не меняются входные данные.

Example:

   

   

def pure_func( List ):

       

     New_List = [ ]

для I в Список :

New_List. append(i * * 2 )

           

     return New_List

       

Original_List = [ 1 , 2 , 3 , 4 ]

MODIFIFIT_LIST = (Original_List) = (Original_List)0056

Печать ( "Оригинальный список:" , Original_List)

( ".

Вывод:

Исходный список: [1, 2, 3, 4]
Измененный список: [1, 4, 9, 16]
 
Рекурсия

Во время функционального программирования нет концепции для 9Цикл 0056 или , а цикл , вместо этого используется рекурсия. Рекурсия — это процесс, в котором функция прямо или косвенно вызывает сама себя. В рекурсивной программе предоставляется решение для базового случая, а решение большей проблемы выражается в терминах меньших проблем. Может возникнуть вопрос, что такое базовый случай? Базовый случай можно рассматривать как условие, которое сообщает компилятору или интерпретатору выйти из функции.

Примечание: Для получения дополнительной информации см. Рекурсию

Пример: Рассмотрим программу, которая находит сумму всех элементов списка без использования цикла for.

   

   

def Sum (L, i, n, count):

       

    

     if n < = i:

         return count

       

     count + = L[i]

       

    

     count = Sum (L, I + 1 , N, COUNT)

Возвращение ГОЛОД

. 0056

       

L = [ 1 , 2 , 3 , 4 , 5 ]

count = 0

n = len (L)

print ( Sum (L, 0 , n, count))

Выход:

15
 
Функции первого класса и могут быть высшего порядка

Первоклассные объекты обрабатываются единообразно во всем. Они могут храниться в структурах данных, передаваться в качестве аргументов или использоваться в управляющих структурах. Говорят, что язык программирования поддерживает первоклассные функции, если он рассматривает функции как первоклассные объекты.

Свойства функций первого класса:

  • Функция является экземпляром типа Object.
  • Вы можете сохранить функцию в переменной.
  • Вы можете передать функцию в качестве параметра другой функции.
  • Вы можете вернуть функцию из функции.
  • Вы можете хранить их в структурах данных, таких как хэш -таблицы, списки,…

DEF 55.0056 return text.upper() 

     

def whisper(text): 

     return text. lower() 

     

def приветствие(функция): 

    

     приветствие = func( функция 9, я передал аргумент 9 am0 как аргумент, я передал функцию 9 am0.0055 ) 

     print (greeting)  

     

greet(shout) 

greet(whisper) 

Output:

ПРИВЕТ, Я СОЗДАН ФУНКЦИЕЙ, ПЕРЕДАННОЙ В КАЧЕСТВЕ АРГУМЕНТА.
привет, меня создала функция, переданная в качестве аргумента.
 

Примечание: Дополнительные сведения см. в разделе Функции первого класса в Python.

Встроенные функции высшего порядка

Чтобы упростить обработку итерируемых объектов, таких как списки и итераторы, в Python реализованы некоторые часто используемые функции высшего порядка. Эти функции возвращают итератор, занимающий мало места. Вот некоторые из встроенных функций высшего порядка:

  • Map(): Функция map() возвращает список результатов после применения данной функции к каждому элементу данной итерации (список, кортеж и т. д.).

    Синтаксис: map(fun, iter)

    Параметры:
    fun: Это функция, в которую map передает каждый элемент данного итерируемого объекта.
    iter: Это итерируемый объект, который необходимо отобразить.

    Тип возвращаемого значения: Возвращает итератор класса карты.

    Пример:

    DEF Дополнение (N):

    . 0056 return n +

         

    numbers = ( 1 , 2 , 3 , 4

    Результаты = Карта (добавление, номера)

    Печать (результаты)

    (результаты)

     

    for result in results:

         print (result, end = " " )

    Output:

    <объект карты по адресу 0x7fae3004b630>
    2 4 6 8
     

    Примечание: Для получения дополнительной информации обратитесь к функции Python map(). верно или нет.

    Синтаксис: фильтр(функция, последовательность)

    Параметры:
    функция: функция, которая проверяет, является ли каждый элемент последовательности истинным или нет.
    последовательность: последовательность, которую необходимо отфильтровать, это могут быть наборы, списки, кортежи или контейнеры любых итераторов.

    Тип возвращаемого значения: возвращает уже отфильтрованный итератор.

    Пример:

         

    def fun(variable): 

           

         letters = [ 'a' , 'e' , 'i , 'o' , 'U' ]

    , если (variable ) ) ). 0007

             return True

         else

             return False

         

         

    sequence = [ 'г' , 'д' , 'д' , 'к' , 5 0 6 90 90, 6 's' , 'p' , 'r'

         

    filtered = filter (fun, sequence) 

     

    print ( 'The filtered letters are:'

       

    for s in filtered: 

         печать (s) 

    Вывод:

    Отфильтрованные письма:
    е
    е
     

    Примечание: Для получения дополнительной информации см. filter() в Python

  • Лямбда-функции: В Python анонимная функция означает, что функция не имеет имени. Как мы уже знаем, ключевое слово def используется для определения обычных функций, а ключевое слово lambda используется для создания анонимных функций.

    Синтаксис:

    аргументы лямбда: выражение
     

    1) Эта функция может иметь любое количество аргументов, но только одно выражение, которое вычисляется и возвращается.
    2) Можно свободно использовать лямбда-функции везде, где требуются функциональные объекты.
    3) Вы должны помнить, что лямбда-функции синтаксически ограничены одним выражением.
    4) Он имеет различные применения в определенных областях программирования помимо других типов выражений в функциях.

    Example:

       

         

    cube = lambda x: x * x *

    print (cube( 7 )) 

         

         

    L = [ 1 , 3 , 2 , 4 , 5 , 6 ]

    is_even = [x for x in L if x % 2 = = 0 ]

       

    print (is_even)  

    Вывод:

    343
    [2, 4, 6]
     

    Примечание: Дополнительные сведения см. в Python lambda.

Неизменяемость

Неизменность — это парадигма функционального программирования, которую можно использовать для отладки, поскольку она выдает ошибку там, где изменяется переменная, а не там, где изменяется значение. Python также поддерживает некоторые неизменяемые типы данных, такие как строка, кортеж, числовое значение и т. д.

Example:

     

   

immutable = "GeeksforGeeks"

   

immutable[ 1 ] = ' К'

Выход:

Traceback (последний последний вызов):
  Файл "/home/ee8bf8d8f560b97c7ec0ef080a077879. py", строка 10, в
    неизменяемый [1] = 'К'
TypeError: объект 'str' не поддерживает назначение элементов
 

Разница между функциональным программированием и объектно-ориентированным программированием

Объектно-ориентированные языки хороши, когда у вас есть фиксированный набор операций над вещами, и по мере развития вашего кода вы в первую очередь добавляете новые вещи. Этого можно добиться, добавив новые классы, которые реализуют существующие методы, а существующие классы не трогают.

Функциональные языки хороши, когда у вас есть фиксированный набор вещей, и по мере развития вашего кода вы в первую очередь добавляете новые операции к существующим вещам. Этого можно добиться, добавив новые функции, которые вычисляют с существующими типами данных, а существующие функции не трогают.

ФУНКЦИОНАЛЬНОЕ ПРОГРАММИРОВАНИЕ ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ
Эта парадигма программирования делает акцент на использовании функций, где каждая функция выполняет определенную задачу. Эта парадигма программирования основана на объектно-ориентированной концепции. Классы используются там, где создаются экземпляры объектов
Основные используемые элементы - это переменные и функции. Данные в функциях неизменяемы (не могут быть изменены после создания). Основные используемые элементы — это объекты и методы, а используемые здесь данные — изменяемые данные.
Следует модели декларативного программирования. Следует модели императивного программирования.
Он использует рекурсию для итерации. Он использует циклы для итерации.
Поддерживается параллельное программирование. Не поддерживает параллельное программирование.
Операторы в этой парадигме программирования не должны следовать определенному порядку во время выполнения. Операторы в этой парадигме программирования должны следовать порядку, то есть восходящему подходу во время выполнения.

Функциональное программирование на Python | Codecademy

Введение

В этой статье мы рассмотрим концепцию функционального программирования, включая его отличия от объектно-ориентированного программирования.

Эта статья разделена на следующие разделы:

  • Введение в функциональное программирование
  • Функциональное и объектно-ориентированное программирование
  • Декларативное и императивное программирование
  • Запись функций в функциональном программировании
  • Использование рекурсии вместо циклов
  • Передача функций в качестве аргументов другим функциям
  • Заключение
Введение в функциональное программирование

Функциональное программирование — это парадигма программирования, в которой код структурирован преимущественно в виде функций. Истоки этого стиля программирования восходят к разделу математики, известному как 9. 1193 лямбда-исчисление , которое изучает функции и их математические свойства. В отличие от популярных объектно-ориентированных и процедурных подходов, функциональное программирование предлагает иной способ мышления при решении задачи.

Давайте рассмотрим, как программировать решения в функциональном стиле с использованием Python.

Функциональное и объектно-ориентированное программирование

В объектно-ориентированном программировании (ООП) вы структурируете свой код на основе концепции объектов. В широко используемых языках ООП (таких как Java или C#) мы создаем шаблон для объекта, используя ключевое слово 9.0055 class , и мы создаем экземпляр объекта, используя ключевое слово new . Эти объекты содержат поля, в которых хранятся данные, и методы, управляющие данными. В этом стиле объект может быть передан в качестве аргумента в функцию другого объекта.

В функциональном программировании вы создаете решение, структурируя код, в основном используя функции. В этой парадигме функции являются «гражданами первого класса», что означает, что мы можем использовать их для хранения данных и управления ими (с ограничениями). Подобно объектам в объектно-ориентированном программировании, в функциональном программировании функции могут быть переданы в качестве аргументов другим функциям и могут быть возвращены другими функциями.

Декларативное и императивное программирование

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

Императивное программирование решает проблему, описывая пошаговое решение. Императивное программирование связано с тем, «как решить проблему».

Напротив, декларативное программирование полагается на базовую структуру или язык программирования для решения проблемы. Единственная задача программиста — описать, какую проблему он хотел бы решить. Декларативное программирование связано с тем, «какую проблему нужно решить».

В качестве аналогии рассмотрим получение чашки кофе. Императивное программирование было бы эквивалентно тому, что вы сами готовите чашку кофе, выполняя следующие шаги:

  • Достаньте кофейник.
  • Загрузите кофейник с кофейной гущей.
  • Добавьте воды в кофейник.
  • Поставьте кофейник на плиту.
  • Включите плиту.
  • Подождите, пока кофе не будет готов.
  • Налейте кофе в чашку.
  • Ты любишь сахар?
    • Если да, добавьте сахар.
    • Если нет, не добавляйте сахар.
  • Выпейте кофе.

Декларативное программирование равносильно тому, что вы идете в местную кофейню и говорите баристе: «Мне, пожалуйста, чашку кофе с одной чайной ложкой сахара». Бариста готовит вам кофе, подает вам чашку, и вы пьете ее, не задумываясь о том, как она была приготовлена.

В качестве аналогии с программированием рассмотрим пример сортировки списка в Python. При обязательной сортировке списка вы реализуете такой алгоритм сортировки:

 
 

lst = [8, 4, 14, 9, 12, 5, 7, 1, 10, 2, 3]

# Сортировка с использованием алгоритма сортировки выбором

для i в диапазоне (len(lst)):

min_idx = i

для j в диапазоне (i+1, len(lst)):

min_idx = j, если lst[j] < lst[min_idx] иначе min_idx

temp = lst[i]

lst [i] = lst[min_idx]

lst[min_idx] = temp

Если бы нам нужно было решить список декларативно в Python, мы бы вызвали list.sort() функция выглядит так:

 
 

lst = [8, 4, 14, 9, 12, 5, 7, 1, 10, 2, 3]

list.sort(lst)

Какой стиль программиста следующие действия могут зависеть от их целей. Если вы ищете краткий код, который эффективно дает вам ответ, вы можете использовать более декларативный подход. Если вы работаете с другими людьми и хотите, чтобы ваш код был простым для понимания, вы можете выбрать более императивный стиль.

ООП и процедурное программирование следуют императивному подходу; функциональное программирование следует декларативному подходу.

Запись функций в функциональном программировании.

Как мы уже говорили во введении к этой статье, центральным понятием функционального программирования является понятие функции; однако существуют требования, которым должна соответствовать функция при ее написании. Первое из этих требований — функция должна быть детерминированной. То есть для любого заданного ввода функция должна возвращать один и тот же вывод при наличии одного и того же набора входных данных. Давайте проиллюстрируем это на следующем примере add function:

 
 

def add(x, y):

return x + y

Эта функция всегда возвращает 7 , если вызывается add(5, 2) .

Другим из этих требований является то, что функции должны быть свободны от как можно большего количества побочных эффектов . Побочный эффект — это когда функция изменяет некоторую внешнюю переменную. Цель состоит в том, чтобы свести к минимуму, а не устранить побочные эффекты. Было бы невозможно устранить все побочные эффекты в функции, потому что программа в конечном итоге должна иметь внешний эффект, чтобы быть полезной.

Чистая функция определяется как детерминированная функция без побочных эффектов.

Рассмотрим следующий пример:

 
 

ans = 0

def add(x, y): # Эта функция имеет побочные эффекты

ans = x + y

определенный таким образом, имеет побочный эффект, потому что он изменяет внешнюю переменную и .

Чтобы сделать это чистой функцией, мы должны записать это как:

 
 

ans = 0

def add(x, y): # Эта функция не имеет побочных эффектов y) Функция больше не изменяет переменную и во время выполнения. Кроме того, переменные x и y существуют только в рамках функции. Работа с ними позволяет функции не иметь побочных эффектов.

Для повторного использования функции всегда должны передаваться все соответствующие параметры, и она не должна полагаться на глобальные переменные.

Рассмотрим пример функции ( app() ), которая заменяет первый и последний элементы списка на 0 :

 
 

lst = [1,2,3,4,5]

def app():

ret = list(lst)

ret[0] = 0

ret[-1] = 0

return ret

zero_list = app()

Запись функции таким образом делает его зависимым от внешней переменной списка с именем lst . Эту функцию нельзя использовать повторно, так как для ее использования в другом месте требуется lst определяется извне. Хотя app() нельзя использовать повторно, он по-прежнему считается свободным от побочных эффектов, поскольку он считывает, но не изменяет переменную lst . Лучший способ написать функцию — передать требуемую переменную списка в качестве аргумента, например:

 
 

lst = [1,2,3,4,5]

def app(l):

ret = list(l)

ret[0] = 0

ret[-1] = 0

return ret

zero_list = app(lst)

Функция app(l) не имеет побочных эффектов и теперь также может использоваться повторно.

Мы заботимся о чистых функциях, потому что по мере усложнения наших программ мы хотим избежать некорректного изменения внешних переменных. Например, если мы вызываем функции из разных потоков, работающих одновременно, нам нужно тщательно сконструировать наши функции, чтобы избежать побочных эффектов. Следование парадигме функционального программирования — отличный способ сделать код чистым и эффективным, а также упростить тестирование.

Использование рекурсии вместо циклов

Для многократного выполнения части кода объектно-ориентированные программисты используют цикл while или цикл for . Циклы не соответствуют стилю функционального программирования, потому что они поддерживают внешний счетчик, который изменяется внутри цикла (побочный эффект!). В функциональном программировании мы используем рекурсию для многократного выполнения кода. Рекурсивные функции обычно записываются в следующем стиле:

 
 

def recursive_function(n):

# Базовый случай для завершения рекурсии

# Вызов recursive_function с новым параметром

Классический пример рекурсивной функции - это вычисление числа Фибоначчи n th

8

8 

def fib(n):

, если n <= 1:

return 1

return fib(n-1) + fib(n-2)

Передача функций в качестве аргументов другим функциям

В традиционном ООП, вы можете передавать объекты в качестве аргументов функции. В функциональном программировании вы можете передавать функции в качестве аргументов другим функциям. Это известно как обработка функций как первоклассных гражданина , термин, который часто можно встретить в промышленности.

Рассмотрим пример:

 
 

def add(x, y):

return x + y

def sub(x, y):

return x - y

def times3(a , b, функция):

return 3 * function(a, b)

add_then_times3 = times3(2, 4, add) # 18

sub_then_times3 = times3(2, 4, sub) # -6

Этот код определяет функцию times3() , которая умножает результат добавьте или вычтите ( sub ) функцию на три и верните значение. Функция times3() также позволяет передавать любую другую функцию, которая принимает два параметра и возвращает целое число.

Функциональное программирование продвигает концепцию краткости при написании кода. Важно быть кратким и писать функции как можно меньшим количеством строк, пока сохраняется удобочитаемость. Мы также можем передать функцию другой функции, записав ее в аргументе с помощью лямбды.

Мы можем сократить предыдущий код, используя лямбду, например:

 
 

def times3(a, b, function):

return 3 * function(a, b)

x, y: x + y) # 18

sub_then_times3 = times3(2, 4, lambda x, y: x - y) # -6

Это выведет тот же результат, что и более подробная реализация ранее. Лямбда-выражения — отличный инструмент, поскольку они позволяют программисту писать функцию, сохраняя при этом поток идей. От программиста больше не требуется останавливаться, писать функцию в другом месте, а затем возобновлять работу. Недостатком лямбда-выражений является то, что они подходят только для реализации простых функций; невозможно записать все функции как лямбда-выражения!

Заключение

Таким образом, функциональное программирование — это мощная парадигма, дающая множество преимуществ. В этой парадигме мы структурируем решения в виде функций.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *