Тонкости использования языка python: часть 3. функциональное программирование
Содержание:
min
- min(iterable, *)
- min(arg1, arg2, *args)
Возвращает минимальный элемент. Две версии функции отличаются аргументами: с итерируемым объектом и со списком аргументов.
print(min()) # 2
print(min(3, 5, 8, 2)) # 2
1 |
print(min(3,5,8,2))# 2 print(min(3,5,8,2))# 2 |
Если коллекция пустая возникнет исключение
print(min([], )) # ValueError
1 | print(min(,))# ValueError |
Именованный аргумент default используется чтобы избежать исключения. Функция min возвращает default только если коллекция пустая:
print(min([], default=0)) # 0
print(min(, default=0)) # 2
1 |
print(min(,default=))# 0 print(min(2,default=))# 2 |
Порядок элементов изменяется аргументом key. Переданная в key функция применяется к каждому элементу. Результат функции используется для определения порядка элементов:
def neg(n):
return -n
print(min(, key=neg)) # 8
1 |
def neg(n) return-n print(min(3,5,8,2,key=neg))# 8 |
Объекты и классы
- – возвращает базовый объект;
- – возвращает идентификатор указанного объекта;
- – возвращает хэш объекта;
- – если объект является экземпляром указанного класса или его подклассом, вернет True;
- – если класс является подклассом другого класса, вернет True;
- – если объект поддерживает вызов, вернет True;
- – представляет указанную функцию методом класса;
- – возвращает строковое представление указанного объекта;
- – устанавливает аттрибут объекта;
- – извлечение значения атрибута объекта;
- – проверяет, имеет ли объект указанный аттрибут;
- – удаление аттрибута;
- – представляет указанную функцию статичным методом;
- – дает возможность использования методов класса-родителя в классе потомке;
- – вернет словарь из аттрибутов объекта.
Best practice
Цикл по списку
Перебрать в цикле не составляет никакого труда, поскольку список – объект итерируемый:
Так как элементами списков могут быть другие итерируемые объекты, то стоит упомянуть и о вложенных циклах. Цикл внутри цикла вполне обыденное явление, и хоть количество уровней вложенности не имеет пределов, злоупотреблять этим не следует. Циклы свыше второго уровня вложенности крайне тяжело воспринимаются и читаются.
Цикл по словарю
Чуть более сложный пример связан с итерированием словарей. Обычно, при переборе словаря, нужно получать и ключ и значение. Для этого существует метод , который создает представление в виде кортежа для каждого словарного элемента.
Цикл, в таком случае, будет выглядеть следующим образом:
Цикл по строке
Строки, по сути своей – весьма простые последовательности, состоящие из символов. Поэтому обходить их в цикле тоже совсем несложно.
Как сделать цикл for с шагом
Цикл с шагом создается при помощи уже известной нам функции , куда, в качестве третьего по счету аргумента, нужно передать размер шага:
Обратный цикл for
Если вы еще не убедились в том, что полезна, то вот ещё пример: благодаря этой функции можно взять и обойти последовательность в обратном направлении.
for в одну строку
Крутая питоновская фишка, основанная на так называемых или, по-русски, генераторов. Их запись, быть может, несколько сложнее для понимания, зато очевидно короче и, по некоторым данным, она работает заметно быстрее на больших массивах данных.
В общем виде генератор выглядит так:
Приведем пример, в котором продублируем каждый символ строки
Другой пример, но теперь уже с условием:
—
Функции как объекты
Создавая объект функции оператором , как было
показано в листинге 1, можно привязать созданный функциональный объект к имени
в точности так же, как можно было бы привязать к этому имени
число или строку .
Этот пример подтверждает статус функций как объектов первого класса в Python. Функция в Python — это всего
лишь ещё одно значение, с которым можно что-то сделать.
Наиболее частое действие, выполняемое с функциональными объектами первого класса, — это передача их во
встроенные функции высшего порядка: ,
и .
Каждая из этих функций принимает объект функции в качестве своего первого аргумента.
-
применяет переданную функцию к каждому элементу в
переданном списке (списках) и возвращает список результатов (той же размерности, что и входной); -
применяет переданную функцию к каждому значению в
списке и ко внутреннему накопителю результата, например,
означает (факториал); -
применяет переданную функцию к каждому элементу списка
и возвращает список тех элементов исходного списка, для которых переданная функция вернула значение
истинности.
Комбинируя эти три функции, можно реализовать неожиданно широкий диапазон операций потока
управления, не прибегая к императивным утверждениям, а используя лишь выражения в функциональном стиле,
как показано в листинге 2 (файл funcH.py из архива python_functional.tgz
в разделе «Материалы для скачивания»):
Листинг 2. Функции высших порядков Python
#!/usr/bin/python # -*- coding: utf-8 -*- import sys def input_arg(): global arg arg = ( lambda: ( len( sys.argv ) > 1 and int( sys.argv ) ) or \ int( input( "число?: " ) ) )() return arg print( 'аргумент = {}'.format( input_arg() ) ) print( list( map( lambda x: x + 1, range( arg ) ) ) ) print( list( filter( lambda x: x > 4, range( arg ) ) ) ) import functools print( '{}! = {}'.format( arg, functools.reduce( lambda x, y: x * y, range( 1, arg ) ) ) )
Примечание. Этот код несколько усложнён по сравнению с предыдущим примером из-за
следующих аспектов, связанных с совместимостью Python версий 2 и 3:
-
Функция , объявленная как встроенная в Python 2, в Python 3
была вынесена в модуль и её прямой вызов по имени
вызовет исключение , поэтому для корректной работы
вызов должен быть оформлен как в примере или включать строку: -
Функции и
в Python 3 возвращают не список (что уже показывалось при обсуждении различий версий), а объекты-итераторы
вида:<map object at 0xb7462bec> <filter object at 0xb75421ac>
Для получения всего списка значений для них вызывается функция .
Поэтому такой код сможет работать в обеих версиях Python:
$ python3 funcH.py 7 аргумент = 7 7! = 720
Если переносимость кода между различными версиями не требуется, то подобные фрагменты можно
исключить, что позволит несколько упростить код.
Отклоненные альтернативны
Альтернативные варианты написания
EXPR as NAME:
Так как конструкция EXPR as NAME уже имеет семантический смысл в выражениях import, except и with, это могло создать ненужную путаницу и некоторые ограничения (например, запрет выражения присваивания внутри заголовков этих конструкций).
(Обратите внимание, что «with EXPR as VAR» не просто присваивает значение EXPR в VAR, а вызывает EXPR.__enter__() и уже после присваивает полученный результат в VAR.)
Дополнительные причины, чтобы предпочесть «:=» выше предложенному написанию:В том случае, если if f(x) as y не бросится вам в глаза, то его можно случайно прочитать как if f x blah-blah, и визуально такая конструкция слишком похожа на if f(x) and y.
Во всех других ситуациях, когда as разрешено, даже читателям со средними навыками приходится прочитывать всю конструкцию от начала, чтобы посмотреть на ключевое слово: import foo as bar
except Exc as var
with ctxmgr() as var
И наоборот, as не относится к оператором if или while и мы преднамеренно создаём путаницу, допуская использование as в «не родной» для него среде.
Также существует «параллель» соответствия междуNAME = EXPR
if NAME := EXPR
Это усиливает визуальное распознавание выражений присваивания.
EXPR -> NAME
Этот синтаксис основан на таких языках, как R и Haskell, ну и некоторых программируемых калькуляторах. (Обратите внимание, что направление стрелки справа-налево y
Но эти проблемы совершенно не связано с другим использованием такой стрелки в Python (в аннотациях возвращаемого типа функции), а просто по сравнению с «:=» (которое восходит к Algol-58) стрелочки менее привычны для присваивания.
Добавление оператора «точка» к именам локальных переменных
Это позволит легко обнаруживать и устраняя некоторые формы синтаксической неоднозначности. Однако такое нововведение стало бы единственным местом в Python, где область видимости переменной закодирована в ее имени, что затрудняет рефакторинг.
Добавление where: к любой инструкции для создания локальных имен:
Порядок выполнения инвертирован (часть с отступом выполнится первой, а затем последует срабатывание «заголовка»). Это потребует введения нового ключевого слова, хотя возможно «перепрофилирование» другого (скорее всего with:). См. PEP 3150, где раннее обсуждался этот вопрос (предложенным там словом являлось given: ).
TARGET from EXPR:
Этот синтаксис меньше конфликтует с другими, чем as (если только не считать конструкции raise Exc from Exc), но в остальном сравним с ними. Вместо параллели с with expr as target: (что может быть полезно, но может и сбить с толку), этот вариант вообще не имеет параллелей ни с чем, но к удивлению лучше запоминается.
as
ТОЛЬКОПреимуществаНедостатки
Особые случаи в генераторах
-
where, let, or given:
Этот способ приводит появлению подвыражения между циклом «for» и основным выражением. Он также вводит дополнительное ключевое слово языка, что может создать конфликты. Из трех вариантов, where является наиболее чистым и читабельным, но потенциальные конфликты всё ещё существуют (например, SQLAlchemy и numpy имеют свои методы where, также как и tkinter.dnd.Icon в стандартной библиотеке).
-
with NAME = EXPR:
Всё тоже самое, как и в верхнем пункте, но используется ключевое слово with. Неплохо читается и не нуждается в дополнительном ключевом слове. Тем не менее, способ более ограничен и не может быть легко преобразован в «петлевой» цикл for. Имеет проблему языка C, где знак равенства в выражении теперь может создавать переменную, а не выполнять сравнение. Также возникает вопрос: «А почему «with NAME = EXPR:» не может быть использовано просто как выражение, само по себе?»
-
with EXPR as NAME:
Похоже на второй вариант, но с использованием as, а не знака равенства. Синтаксически родственно другими видами присваивания промежуточных имён, но имеет те же проблемы с циклами for. Смысл при использованием ключевого слова with в генераторах и в качестве отдельной инструкции будет совершенно различным
with