Тънкостите на използването на езика Python Част 3
Функции в Python
Функциите в Python се дефинират по 2 начина: чрез дефинициятаdef или чрез анонимната декларацияlambda. И двете дефиниции са налични в различна степен в някои други езици за програмиране. Характеристика на Python е, че функцията е точно толкова наречена обект, колкото всеки друг обект от някакъв тип данни, да речем, целочислена променлива. Списък 1 показва най-простия пример (файлътfunc.py от архиваpython_functional.tgz в секцията за изтегляне):
Листинг 1. Дефиниции на функции
Когато извикваме и трите функционални обекта, получаваме същия резултат:
Това е още по-ясно изразено във версия 3 на Python, в която всичко е клас (включително целочислена променлива), а функциите са програмни обекти, принадлежащи към класаfunction :
Забележка. Има още 2 вида обекти, които позволяват функционално извикване - метод на функционален клас и функтор, за които ще говорим по-късно.
Ако функционалните обекти на Python са същите обекти като другите обекти с данни, тогава можете да правите всичко с тях, което можете да правите с всякакви данни:
- динамичнопромяна по време на изпълнение;
- вграждане в по-сложни структури от данни (колекции);
- предават като параметри и връщат стойности и т.н.
На това (манипулиране на функционални обекти като обекти с данни) се основава функционалното програмиране. Python, разбира се, не е истински функционален език за програмиране, така че за напълно функционаленпрограмиране, има специални езици: Lisp, Planner и по-нови: Scala, Haskell. Ocaml, . Но в Python можете да "вградите" техники за функционално програмиране в общия поток от императивен (команден) код, например, като използвате методи, заимствани от пълноценни функционални езици. Тези. "сгъване" на отделни фрагменти от императивен код (понякога доста големи) във функционални изрази.
Понякога хората питат: "Какви са предимствата на функционалния стил на писане на отделни фрагменти за програмиста?". Основното предимство на функционалното програмиране е, че след веднъж отстраняване на грешки на такъв фрагмент, той няма да причини грешки при последваща употреба поради странични ефекти, свързани с присвояване и конфликти на имена.
Доста често при програмиране на Python се използват типични конструкции от областта на функционалното програмиране, например:
В резултат на бягане получаваме:
Функции като обекти
Чрез създаване на функционален обект с оператораlambda, както е показано в списък 1, можете да свържете създадения функционален обект към иметоpow3 по точно същия начин, по който бихте могли да свържете числото123 или низа"Hello!" към това име. Този пример потвърждава статуса на функциите като първокласни обекти в Python. Функция в Python е просто още една стойност, с която можете да направите нещо.
Най-често срещаното нещо, което правите с първокласни функционални обекти, е да ги предадете на вградените функции от по-висок ред:map(),reduce() иfilter(). Всяка от тези функции приема функционален обект като първи аргумент.
- map() прилага предадената функция към всекиелемент в предадения списък(и) и връща списък с резултати (със същото измерение като входа);
- reduce() прилага предадената функция към всяка стойност в списъка и към вътрешния акумулатор на резултата, напримерreduce( lambda n,m: n * m, range( 1, 10 ) ) означава10! (факториал);
- filter() прилага предадената функция към всеки елемент от списъка и връща списък с тези елементи в оригиналния списък, за които предадената функция е върнала истинска стойност.
Комбинирайки тези три функции, можете да приложите неочаквано широк набор от операции на контролен поток, без да прибягвате до императивни изрази, но като използвате само изрази във функционален стил, както е показано в листинг 2 (файлътfuncH.py от архиваpython_functional.tgz в секцията „Изтегляния“):
Листинг 2. Функции от по-висок ред на Python
Забележка: Този код е малко по-сложен от предишния пример поради следните аспекти, свързани със съвместимостта на Python версия 2 и 3:
- Функциятаreduce(), декларирана като вградена в Python 2, беше преместена в модулаfunctools в Python 3 и нейното директно извикване по име ще хвърли изключениеNameError, следователно за правилна работа извикването трябва да бъде форматирано както в примера или да включва реда:from functools import *
- Функциитеmap() иfilter() в Python 3 не връщат списък (както вече беше показано в обсъждането на разликите във версиите), а итераторни обекти на формата:
За да получите целия списък със стойности за тях, се извиква функциятаlist().
Следователно този код ще работи и в двете версии на Python:
Ако не се изисква преносимост на код между различни версии, тогава подобни фрагментимогат да бъдат изключени, което донякъде ще опрости кода.
Във функционалното програмиране рекурсията е основният механизъм, подобно на циклите в итеративното програмиране.
В някои дискусии за Python многократно срещах твърдения, че в Python дълбочината на рекурсия е ограничена „хардуерно“ и следователно някои действия не могат да бъдат приложени по принцип. Интерпретаторът на Python има ограничение на дълбочината на рекурсия по подразбиране от 1000, но това е числов параметър, който винаги може да бъде нулиран, както е показано в листинг 3 (пълният примерен код може да бъде намерен във файлаfact2.py от архиваpython_functional.tgz в секцията "Изтегляния"):
Листинг 3. Факторно изчисление с произволна дълбочина на рекурсия
Ето как изглежда изпълнението на този пример в Python 3 и в Python2 (въпреки че всъщност е малко вероятно полученото число да се побере на един екран на конзолния терминал):
Няколко прости примера
Нека извършим няколко прости трансформации на обичайния императивен код (команда, оператор), за да превърнем отделните му фрагменти във функционални. Първо, нека заменим операторите за разклонения с логически условия, които поради "отложени" (мързеливи, мързеливи) изчисления ни позволяват да контролираме изпълнението или неизпълнението на отделни разклонения на кода. И така, наложителната конструкция:
- напълно еквивалентен на следния функционален фрагмент (поради "отложените" възможности на логическите оператории иили ):
Нека използваме факториела отново като пример. Листинг 4 показва функционалния код за изчисляване на факториела (файлfact1.py в архиваpython_functional.tgz в секцията "Изтегляния"):
Списък 4.Операторна (императивна) дефиниция на факториел
Аргументът за изчисляване се взема от стойността на опцията на командния ред (ако има такава) или се въвежда от терминала. Първият вариант, показан по-горе, вече е приложен в листинг 2, където е заменен с функционални изрази:
- дефиниция на факторна функция:
- заявка за въвеждане на стойността на аргумента от терминалната конзола:
Във файлаfact3.py се появява друга дефиниция на функция, направена чрез функцията от по-висок редreduce() :
Тук също опростяваме израза заn, намалявайки го до едно извикване на анонимна (ненаименована) функция:
И накрая, може да забележите, че присвояването на стойност на променливатаn е необходимо само за нейното използване в извикването наprint() за отпечатване на тази стойност. Ако премахнем и това ограничение, тогава цялото приложение ще се дегенерира в един единствен функционален оператор (вижте файлаfact4.py в архиваpython_functional.tgz в секцията „Изтегляния“):
Това единично извикване във функциятаprint() представя цялото приложение в неговата функционална форма:
Този код (файлът fact4.py) чете ли се по-добре от императивната нотация (файлът fact1.py)? По-скоро не, отколкото да. Тогава каква е неговата заслуга? Фактът, че скаквито и да е промени в заобикалящия го код, нормалната работа натози фрагмент ще бъде запазена, тъй като няма риск от странични ефекти поради промени в стойностите на използваните променливи.
Функции от по-висок ред
Във функционалния стил на програмиране стандартна практика единамично да се генерира функционален обект по време на изпълнение на код и след това да се извика в същия код. Има редица области, в които такиватехника може да бъде полезна.
Една от интересните концепции на функционалното програмиране еclosures (затваряне). Тази идея се оказа толкова примамлива за много разработчици, че дори беше приложена в някои нефункционални езици за програмиране (Perl). Дейвид Мерц дава следното определение за затваряне: „Затварянето е процедура с набор от данни, свързани с него“ (за разлика от обектите в програмирането на обекти, като: „данни с набор от процедури, свързани с него“).
Значението на затварянето е, че дефиницията на функцията "замразява" контекста около нея замомента на дефиниране. Това може да стане по различни начини, например чрез параметризиране на създаването на функцията, както е показано в листинг 5 (файлътclos1.py в архиваpython_functional.tgz в секцията „Изтегляния“):
Листинг 5. Създаване на затваряне
Ето как работи такава динамично дефинирана функция:
Друг начин за създаване на затваряне е да използвате стойността на параметъра по подразбиране в точката на дефиницията на функцията, както е показано в списък 6 (файлътclos3.py от архиваpython_functional.tgz в раздела за изтегляне):
Списък 6. Друг начин за създаване на затваряне
Никакви последващи присвоявания на параметри по подразбиране няма да променят предварително дефинираната функция, но самата функция може да бъде заменена:
Приложение на частична функция
Частичното прилагане на функция предполага, въз основа на функцията наN променливи, дефинирането на нова функция с по-малък бройM N променливи, докато останалитеN — M променливи получават фиксирани "замразени" стойности (използва се модулътfunctools ). Такъв пример би билобсъдени по-долу.
Функторът не е функция, а обект от клас, който дефинира метод с име__call__(). В този случай извикването може да бъде приложено към екземпляр на такъв обект, по същия начин, както се случва за функциите. Листинг 7 (файлътpart.py от архиваpython_functional.tgz в секцията за изтегляне) демонстрира използването на затваряне, частична дефиниция на функция и функтор, които всички постигат един и същ резултат.
Листинг 7. Сравняване на затваряне, частична дефиниция и функтор
Извикването на всичките три конструкции за аргумент, равен на 5, ще доведе до същия резултат, въпреки че те ще използват напълно различни механизми:
Curry (или currying, curring) е трансформирането на функция от много променливи във функция, която приема своите аргументи един по един.
Забележка. Тази трансформация е въведена от М. Шейнфинкел и Г. Фреге и е получила името си в чест на математика Хаскел Къри, на когото е кръстен и програмният език Хаскел.
Currying не е уникално за функционалното програмиране, тъй като currying може да бъде написано на Perl или C++, например. Curry операторът дори е вграден в някои езици за програмиране (ML, Haskell), което позволява многоместни функции да бъдат прехвърлени към curried представяне. Но всички езици, които поддържат затваряния, ви позволяват да пишете curried функции и Python не е изключение.
Листинг 8 показва най-простия пример с използване на currying (файлътcurry1.py в архиваpython_functional.tgz в секцията "Изтегляния"):
Списък 8. Къри
Ето как изглежда изпълнението на тези повиквания:
Заключение
В следващата статия ще обсъдим организацията на паралелното изпълнение на код в средата на Python.