Python Функціональне програмування - один із 4-ох основних стилів програмування

Python Функціональне програмування обов'язково потрібно постаратися освоїти, щоб використовувати переваги, які воно надає.

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

Функціональне програмування є однією з парадигм, що підтримуються мовою програмування Python.

Основними передумовами для повноцінного функціонального програмування в Python є функції вищих порядків, розвинені засоби обробки списків, рекурсія, можливість організації лінивих обчислень.

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

Зміст

  • 1 Можливості
  • 1.1 Визначення та використання функції
  • 1.2 Спискові включення
  • 1.3 Вбудовані функції вищих систем
  • 1.3.1 map()
  • 1.3.2 filter()
  • 1.3.3 reduce()
  • 1.3.4 apply()
  • 1.4 Замикання
  • 1.5 Ітератори
  • 1.6 Модуль functools
  • 1.7 Ліниві обчислення
  • 1.8 Функтори
  • 2 Примітки
  • 3 Посилання
  • 4 Література

Можливості

Визначення та використання функції

Функція Python може бути визначена за допомогою оператораdefабо лямбда-виразом. Наступні оператори еквівалентні:

def func(x , y ): return x ** 2 + y ** 2

func = lambda x , y : x ** 2 + y ** 2

У визначенні функції фігурують формальні аргументи. Деякі з них можуть мати значення за промовчанням. Усі аргументи зі значеннями за промовчанням слідують після аргументів без значень за промовчанням.

При виклику функціїзадаються фактичні аргументи. Наприклад:

Спочатку йдуть позиційні аргументи. Вони порівнюються з іменами формальних аргументів по порядку. Потім йдуть іменовані аргументи. Вони зіставляються за іменами і може бути задані у виклику функції у порядку.

Вочевидь, всі аргументи, котрим у описі функції не зазначені значення за промовчанням, мають бути у виклику функції. Повтори в іменах аргументів неприпустимі.

Функція завжди повертає лише одне значення (абоNone, якщо значення не задано в операторіreturnабо цей оператор не зустрінутий після досягнення кінця визначення функції). Однак, це незначне обмеження, оскільки значенням, що повертається, може бути кортеж.

Визначивши функцію з допомогою лямбда-выражения, можна відразу використовувати:

>>> ( lambda x : x + 2 )( 5 ) 7

Лямбда-вирази зручні для визначення не дуже складних функцій, які передаються потім іншим функціям.

Функції в Python є об'єктами першого класу, тобто, можуть використовуватися у програмі поруч із об'єктами інших типів даних.

Спискові включення

Спискове включення (англ. list comprehension ) - найбільш виразне з функціональних засобів Python. Наприклад, для обчислення списку квадратів позитивних цілих чисел менших 10 можна використовувати вираз:

Вбудовані функції вищих порядків

У Python є функції, одним із аргументів яких є інші функції:map(),filter(),reduce(),apply().

Функціяmap()дозволяє обробляти одну або кілька послідовностей за допомогою заданої функції:

Аналогічного (тільки при однаковій довжині списків) результату можна досягти за допомогоюспискових виразів:

Функціяfilter()дозволяє фільтрувати значення послідовності. У результуючому списку лише ті значення, для яких значення функції для елемента є істинним:

Те саме за допомогою спискових виразів:

Для організації ланцюжкових обчислень у списку можна використовувати функціюreduce(). Наприклад, добуток елементів списку може бути обчислений так (Python 2):

Обчислення відбуваються у такому порядку:

Ланцюжок викликів зв'язується за допомогою проміжного результату (res). Якщо список порожній, просто використовується третій параметр (у разі нуля множників це 1):

Зрозуміло, проміжний результат є необов'язковим числом. Це може бути будь-який інший тип даних, зокрема список. Наступний приклад показуєреверссписку:

Для найбільш поширених операцій у Python є вбудовані функції:

У Python 3 вбудованої функціїreduce()немає, але її можна знайти в модуліfunctools.

Функція для застосування іншої функції до позиційних та іменованих аргументів, заданих списком та словником відповідно (Python 2):

У Python 3 замість функціїapply()слід використовувати спеціальний синтаксис:

Функції, що визначаються всередині інших функцій, є повноцінними замикання (англ.closures):

Інші засоби функціонального програмування доступні зі стандартної бібліотеки (наприклад, модульitertools) та інших бібліотек.

Наступний приклад ілюструє застосування перелічуючого і сортуючого ітераторів (ітератор не може бути надрукований операторомprint, тому значення, що залишилися в ньому, були поміщені в список):

Наступний приклад ілюструє використання модуляitertools:

У наступному прикладі ілюструється функціяgroupby(групувати за), за допомогою якої породжується список пар значення ключа і відповідний ключ ітератор (в цей ітератор зібрані всі значення вихідного списку з однаковим значенням ключа). У прикладі ключем є True чи False залежно від позитивності значення. (Для цілей виведення кожен ітератор перетворюється на список).

У модуліitertoolsє й інші функції для роботи з ітераторами, що дозволяють коротко (у функціональному стилі) і з обчислювальної точки зору ефективно висловити необхідні процеси обробки списків.

Модуль functools

У Python 2.5 з'явився модульfunctoolsі зокрема можливістьчасткового застосування функцій:

(Часткове застосування функцій також можна реалізувати за допомогою замикань чи функторів)

Лениві обчислення

Ліниві обчислення можна організувати в Python кількома способами, використовуючи різні механізми:

  • найпростіші логічні операції or та and не обчислюють другий операнд, якщо результат визначається першим операндом
  • лямбда-вирази
  • визначені користувачем класи з лінивою логікою обчислень або функтори
  • Генератори та генераторні вирази
  • (Python 2.5) if-вираз має «ліниву» семантику (обчислюється тільки той операнд, який потрібен)

Приклад, що ілюструє роботу if-виразу. За допомогою оператораprintможна простежити, які функції реально викликали:

Деякі приклади з книги рецептів:

  • Лінива сортування
  • Лінивий обхід графа
  • Ліниве обчислення властивостей
  • Каррінг

Функтори

Функтораминазивають об'єкти, синтаксично подібні до функцій, тобто підтримують операцію виклику. Для визначення функтора потрібно перевантажити оператор()за допомогою методу__call__.

У Python функтори повністю аналогічні функціям, крім спеціальних атрибутів (func_codeта інших). Наприклад, функтори можна передавати як функції зворотного виклику (callback) в С-код.

Функтори дозволяють замінити деякі прийоми, пов'язані з використанням замикання, статичних змінних тощо.

Нижче представлене замикання та еквівалентний йому функтор:

Слід зазначити, що код, що використовує замикання, виконуватиметься швидше, ніж код із функтором. Це пов'язано з необхідністю отримання атрибутуvalу змінноїself(тобто функтор робить одну Python операцію більше).

Функтори також не можна використовувати для створення декораторів з параметрами.

З іншого боку, функторам доступні всі можливості ОВП у Python, що робить їх дуже корисними для функціонального програмування.

Наприклад, можна написати функтор, який «запам'ятовуватиме» виконувані над ним операції і потім повторювати їх. І тому досить відповідним чином перевантажити спеціальні методи.

Функтори привносять у Python можливість лінивих обчислень, властиву функціональним мовам: замість обчислення результату висловлювання — динамічне визначення нових функцій комбінуванням наявних.

Певний функцій створює значні накладні витрати, так як при кожному виклику проходить по викликах всіх вкладенихlambda.

Можна оптимізувати функтор, застосувавши техніку генерування байт-коду під час виконання. Відповідний приклад та тести на швидкість є в ПрикладахPython програм.

При використанні цієї техніки швидкість виконання не відрізнятиметься від «статичного» коду (якщо не брати до уваги часу, що потрібно на одноразове конструювання результуючої функції).

Замість байт-коду Python можна генерувати на виході, наприклад, код мовою програмування C, іншими мовами програмування або файлами XML.

Незважаючи на накладні витрати, ліниве обчислення може дати помітний виграш у швидкості у випадках, коли дії, що обертаються лінивим функтором, досить дорогі, наприклад, включають об'ємні обчислення або доступ до диска.

Припустимо деякий проміжний результатXліниво обчислюється перед умовним оператором; для нього буде створено ланцюжок функторів.

У тій галузі умовного оператора, де значенняXне потрібно по ходу обчислення, цей ланцюжок функторів буде просто відкинуто, не привівши до дорогого обчислення.

В іншій гілці, деXпотрібно для обчислення кінцевого результату функції, ланцюжок функторов здійснить його обчислення.

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