Функции в программировании
Содержание:
Введение в range()
Итак, как работает функция Python под названием range? Простыми словами, range() позволяет вам генерировать ряд чисел в рамках заданного диапазона. В зависимости от того, как много аргументов вы передаете функции, вы можете решить, где этот ряд чисел начнется и закончится, а также насколько велика разница будет между двумя числами.
Вот небольшой пример range() в действии:
Python
for i in range(3, 16, 3):
quotient = i / 3
print(f»{i} делится на 3, результат {int(quotient)}.»)
1 |
foriinrange(3,16,3) quotient=i3 print(f»{i} делится на 3, результат {int(quotient)}.») |
В этом цикле вы просто можете создать ряд чисел, кратных трем, так что вам не нужно вводить каждое из них лично.
Например, следующее использование range() едва ли можно назвать Питоническим (это плохой пример):
Python
captains =
for i in range(len(captains)):
print(captains)
1 |
captains=’Janeway’,’Picard’,’Sisko’ foriinrange(len(captains)) print(captainsi) |
range() отлично подходит для создания повторяющихся чисел, но это не самый лучший выбор, если вам нужно перебрать данные, которые могут быть зациклены с помощью оператора in.
Есть три способа вызова range():
- range(стоп) берет один аргумент
- range(старт, стоп) берет два аргумента
- range(старт, стоп, шаг) берет три аргумента
Вызывая range() с одним аргументом, вы получите ряд чисел, начинающихся с 0 и включающих каждое число до, но не включая число, которое вы обозначили как конечное (стоп).
Как это выглядит на практике:
Python
for i in range(3):
print(i)
1 |
foriinrange(3) print(i) |
Выдача вашего цикла будет выглядеть так:
Python
0
1
2
1 |
1 |
Проверим: у нас есть все числа от 0 до, но не включая 3 — числа, которое вы указали как конечное.
range(старт, стоп)
Вызывая range() с двумя аргументами, вам нужно решить не только, где ряд чисел должен остановиться, но и где он должен начаться, так что вам не придется начинать с нуля каждый раз. Вы можете использовать range() для генерации ряда чисел, начиная с А до Б, используя диапазон (А, Б). Давайте узнаем, как генерировать диапазон, начинающийся с 1.
Попробуем вызывать range() с двумя аргументами:
Python
for i in range(1, 8):
print(i)
1 |
foriinrange(1,8) print(i) |
Ваша выдача будет выглядеть следующим образом:
Python
1
2
3
4
5
6
7
1 |
1 2 3 4 5 6 7 |
Отлично: у вас есть все числа от 1 (число, которые вы определили как стартовое), до, но не включая, 8 (число, которые вы определили как конечное).
Но если вы добавите еще один аргумент, то вы сможете воспроизвести ранее полученный результат, когда пользуетесь списком под названием numbers_divisible_by_three.
range(старт, стоп, шаг)
Вызывая range() с тремя аргументами, вы можете выбрать не только то, где ряд чисел начнется и остановится, но также то, на сколько велика будет разница между одним числом и следующим. Если вы не задаете этот «шаг», то range() автоматически будет вести себя так, как если бы шаг был бы равен 1.
Обратите внимание: шаг может быть положительным, или отрицательным числом, но он не может равняться нулю:
Python
>>> range(1, 4, 0)
Traceback (most recent call last):
File «<stdin>», line 1, in <module>
ValueError: range() arg 3 must not be zero
1 |
>>>range(1,4,) Traceback(most recent call last) File»<stdin>»,line1,in<module> ValueErrorrange()arg3must notbe zero |
Если вы попробуете использовать 0 как шаг, вы получите ошибку ValueError.
Теперь, так как вы знаете, как использовать шаг, вы можете снова использовать цикл, который мы видели ранее, с числами, кратными 3.
Попробуйте лично:
Python
for i in range(3, 16, 3):
quotient = i / 3
print(f»{i} делится на 3, результат {int(quotient)}.»)
1 |
foriinrange(3,16,3) quotient=i3 print(f»{i} делится на 3, результат {int(quotient)}.») |
Ваша выдача будет выглядеть абсолютно так же, как выдача для цикла for, которую мы видели ранее в данном руководстве, когда мы использовали список numbers_divisible_by_three:
Python
3 делится на 3, результат 1.
6 делится на 3, результат 2.
9 делится на 3, результат 3.
12 делится на 3, результат 4.
15 делится на 3, результат 5.
1 |
3делитсяна3,результат1. 6делитсяна3,результат2. 9делитсяна3,результат3. 12делитсяна3,результат4. 15делитсяна3,результат5. |
Как вы видите в этом примере, вы можете использовать аргумент шаг для увеличения в сторону больших чисел. Это называется инкрементация.
Встроенные функции высшего порядка
К встроенным функциям высшего порядка, которые можно использовать без импорта каких-либо библиотек, относятся map и filter.
Функция map принимает функцию и итератор, возвращает итератор, элементами которого являются результаты применения функции к элементам входного итератора.
Примеры:
>>> a = >>> list(map(lambda x: x**2, a)) >>> def fun(x): if x % 2 == 0: return 0 else: return x*2 >>> list(map(fun, a))
Функция filter принимает функцию предикат и итератор, возвращает итератор, элементами которого являются данные из исходного итератора, для которых предикат возвращает True.
Примеры:
>>> list(filter(lambda x: x > 0, )) >>> list(filter(lambda x: len(x) == 2, ))
Если придерживаться “питонического” стиля программирования, то вместо map и filter лучше использовать списковое включение (list comprehensions) с круглыми скобками (в этом случае будет создан генератор, более подробно см. “Python. Урок 7. Работа со списками (list)“):
Вариант с функцией map
map(lambda x: x**2, )
можно заменить на:
(v**2 for v in )
Для варианта с функцией filter
filter(lambda x: x > 0, )
аналог будет выглядеть так:
(v for v in if v > 0)
В экосистеме Python есть два модуля: functools и operator, которые развивают идею использования HOF и предоставляют инструменты для разработки программ в функциональном стиле.
Протоколы
Такие термины как «протокол итератора» или «протокол дескрипторов» уже привычны и используются давно.
Теперь можно описывать протоколы в виде кода и проверять их соответствие на этапе статического анализа.
Стоит отметить, что начиная с Python 3.6 в модуль typing уже входят несколько стандартных протоколов.
Например, (требующего наличие метода ), (требует ) и некоторых других.
Описание протокола
Протокол описывается как обычный класс, наследующийся от Protocol. Он может иметь методы (в том числе с реализацией) и поля.
Реальные классы, реализующие протокол могут наследоваться от него, но это не обязательно.
Мы можете комбинировать протоколы с помощью наследования, создавая новые.
Однако в этом случае вы так же должны явно указать Protocol как родительский класс
Дженерики, self-typed, callable
Протоколы как и обычные классы могут быть Дженериками. Вместо указания в качестве родителей и можно просто указать
Кроме того, протоколы могут использоваться в тех случаях, когда синтаксиса аннотации недостаточно.
Просто опишите протокол с методом нужной сигнатуры
Проверки в рантайме
Хотя протоколы и рассчитаны в первую очередь на использование статическими анализаторами, иногда бывает нужно проверить принадлежность класса нужному протоколу.
Чтобы это было возможно, примените к протоколу декоратор и / проверки начнут проверять соответствие протоколу
Однако такая возможность имеет ряд ограничений на использование. В частности, не поддерживаются дженерики
Важность функций
Абстракция
Человек бежит, машина едет, корабль плывёт, а самолёт летит. Всё это – объекты реального мира, которые выполняют однотипные действия. В данном случае, они перемещаются во времени и пространстве. Мы можем абстрагироваться от их природы, и рассматривать эти объекты с точки зрения того, какое расстояние они преодолели, и сколько времени на это ушло.
Мы можем написать функцию, которая вычисляет скорость в каждом конкретном случае
Нам не важно, кто совершает движение: и для человека и для самолёта средняя скорость будет рассчитываться одинаково
Это простой пример и простая функция, но абстракции могут быть куда более сложными. И именно тогда раскрывается настоящая сила функций. Вместо того чтобы решать задачу для каждого конкретного случая, проще написать функцию, которая находит решение для целого ряда однотипных, в рамках применяемой абстракции, объектов. В случае сложных и длинных вычислений, это повлечёт за собой значительное сокращение объёмов кода, а значит и времени на его написание.
Возможность повторного использования
Функции были созданы ради возможности их многократного применения. Код без функций превратился бы в огромное нечитаемое полотно, на порядки превышающее по длине аналогичную программу с их использованием.
Например, при работе с массивами чисел, вам нужно часто их сортировать. Вместо того чтобы реализовать простой алгоритм сортировки (или использовать встроенную функцию), вам пришлось бы каждый раз перепечатывать тело этой или похожей функции:
Всего 10 таких сортировок, и привет, лишние 60 строк кода.
Модульность
Разбитие больших и сложных процессов на простые составляющие – важная часть, как кодинга, так и реальной жизни. В повседневности мы занимаемся этим неосознанно. Когда убираемся в квартире, мы пылесосим, моем полы и окна, очищаем поверхности от пыли и наводим блеск на всё блестящее. Всё это – составляющие одного большого процесса под названием «уборка», но каждую из них также можно разбить на более простые подпроцессы.
В программировании модульность строится на использовании функций. Для каждой подзадачи – своя функция. Такая компоновка в разы улучшает читабельность кода и уменьшает сложность его дальнейшей поддержки.
Допустим, мы работаем с базой данных. Нам нужна программа, которая считывает значения из базы, обрабатывает их, выводит результат на экран, а затем записывает его обратно в базу.
Без применения модульности получится сплошная последовательность инструкций:
Но если вынести каждую операцию в отдельную функцию, то текст главной программы получится маленьким и аккуратным.
Это и называется модульностью.
Пространство имен
Концепция пространства имён расширяет понятие модульности. Однако цель – не облегчить читаемость, а избежать конфликтов в названиях переменных.
Пример из жизни: в ВУЗе учатся два человека с совпадающими ФИО. Их нужно как-то различать. Если сделать пространствами имён группы этих студентов, то проблема будет решена. В рамках своей группы ФИО этих студентов будут уникальными.
Функции
Последнее обновление: 11.04.2018
Функции представляют блок кода, который выполняет определенную задачу и который можно повторно использовать в других частях программы. Формальное определение функции:
def имя_функции (): инструкции
Определение функции начинается с выражения def, которое состоит из имени функции, набора скобок с параметрами и двоеточия.
Параметры в скобках необязательны. А со следующей строки идет блок инструкций, которые выполняет функция. Все инструкции функции имеют отступы от начала строки.
Например, определение простейшей функции:
def say_hello(): print("Hello")
Функция называется . Она не имеет параметров и содержит одну единственную инструкцию,
которая выводит на консоль строку «Hello».
Для вызова функции указывается имя функции, после которого в скобках идет передача значений для всех ее параметров. Например:
def say_hello(): print("Hello") say_hello() say_hello() say_hello()
Здесь три раза подряд вызывается функция say_hello. В итоге мы получим следующий консольный вывод:
Hello Hello Hello
Теперь определим и используем функцию с параметрами:
def say_hello(name): print("Hello,",name) say_hello("Tom") say_hello("Bob") say_hello("Alice")
Функция принимает параметр name, и при вызове функции мы можем передать вместо параметра какой-либо значение:
Hello, Tom Hello, Bob Hello, Alice
Значения по умолчанию
Некоторые параметры функции мы можем сделать необязательными, указав для них значения по умолчанию при определении функции. Например:
def say_hello(name="Tom"): print("Hello,", name) say_hello() say_hello("Bob")
Здесь параметр name является необязательным. И если мы не передаем при вызове функции для него значение, то применяется значение по умолчанию, то есть
строка «Tom».
Именованные параметры
При передаче значений функция сопоставляет их с параметрами в том порядке, в котором они передаются. Например, пусть есть следующая функция:
def display_info(name, age): print("Name:", name, "\t", "Age:", age) display_info("Tom", 22)
При вызове функции первое значение «Tom» передается первому параметру — параметру name, второе значение — число 22 передается второму параметру — age. И так далее по порядку.
Использование именованных параметров позволяет переопределить порядок передачи:
def display_info(name, age): print("Name:", name, "\t", "Age:", age) display_info(age=22, name="Tom")
Именованные параметры предполагают указание имени параметра с присвоением ему значения при вызове функции.
Неопределенное количество параметров
С помощью символа звездочки можно определить неопределенное количество параметров:
def sum(*params): result = 0 for n in params: result += n return result sumOfNumbers1 = sum(1, 2, 3, 4, 5) # 15 sumOfNumbers2 = sum(3, 4, 5, 6) # 18 print(sumOfNumbers1) print(sumOfNumbers2)
В данном случае функция sum принимает один параметр — , но звездочка перед названием параметра указывает, что фактически на место этого
параметра мы можем передать неопределенное количество значений или набор значений. В самой функции с помощью цикла for можно пройтись по этому набору и произвести с переданными
значениями различные действия. Например, в данном случае возвращается сумма чисел.
Возвращение результата
Функция может возвращать результат. Для этого в функции используется оператор return, после которого указывается возвращаемое значение:
def exchange(usd_rate, money): result = round(money/usd_rate, 2) return result result1 = exchange(60, 30000) print(result1) result2 = exchange(56, 30000) print(result2) result3 = exchange(65, 30000) print(result3)
Поскольку функция возвращает значение, то мы можем присвоить это значение какой-либо переменной и затем использовать ее: .
В Python функция может возвращать сразу несколько значений:
def create_default_user(): name = "Tom" age = 33 return name, age user_name, user_age = create_default_user() print("Name:", user_name, "\t Age:", user_age)
Здесь функция create_default_user возвращает два значения: name и age. При вызове функции эти значения по порядку присваиваются переменным
user_name и user_age, и мы их можем использовать.
Функция main
В программе может быть определено множество функций. И чтобы всех их упорядочить, хорошей практикой считается добавление специальной функции
, в которой потом уже вызываются другие функции:
def main(): say_hello("Tom") usd_rate = 56 money = 30000 result = exchange(usd_rate, money) print("К выдаче", result, "долларов") def say_hello(name): print("Hello,", name) def exchange(usd_rate, money): result = round(money/usd_rate, 2) return result # Вызов функции main main()
НазадВперед
Элементы функционального подохда к программированию
При написании
программ приветствуется такой подход, который называется функциональным
программированием. Продемонстрирую его на следующем примере. Предположим,
нам нужна функция, которая находит максимальное значение из двух чисел:
def max2(a, b): if a > b: return a return b
И вызвать мы ее
можем так:
print( max2(2, -3) )
Затем, нам
потребовалась функция, которая бы находила максимальное из трех чисел. Как ее
можно реализовать? Используя идею функционального программирования, это можно
сделать следующим образом:
def max3(a, b, c): return max2(a, max2(b, c))
И вызвать так:
print( max3(2, -3, 5) )
Смотрите, здесь
оператор return возвращает
значение, которое возвращает функция max2. Но, прежде чем она будет
выполнена, вызовется другая функция max2, которая определит максимальное
среди чисел b и c. То есть,
прежде чем вызвать первую функцию max2 необходимо вычислить ее
параметры: первый просто берется их x, а второй вычисляется вложенной
функцией max2. Вот так это
работает и вот что из себя представляет элемент функционального подхода к
программированию.
Причем,
благодаря гибкости языка Python, мы можем вызвать эту функцию и
для нахождения максимальной строки:
print( max3("ab", "cd", "abc") )
так как строки
могут спокойно сравниваться между собой. И вообще, любые величины, которые
можно сравнивать на больше и меньше, можно подставлять в качестве аргументов
функции max3 и max2.
Функции в Python
Функции в Python определяются 2-мя способами: через определение
или через анонимное описание . Оба этих способа
определения доступны, в той или иной степени, и в некоторых других языках программирования. Особенностью
Python является то, что функция является таким же именованным объектом, как и любой другой объект некоторого
типа данных, скажем, как целочисленная переменная. В листинге 1 представлен простейший пример (файл
func.py из архива python_functional.tgz в разделе «Материалы для скачивания»):
Листинг 1. Определения функций
#!/usr/bin/python # -*- coding: utf-8 -*- import sys def show( fun, arg ): print( '{} : {}'.format( type( fun ), fun ) ) print( 'arg={} => fun( arg )={}'.format( arg, fun( arg ) ) ) if len( sys.argv ) > 1: n = float( sys.argv ) else: n = float( input( "число?: " ) ) def pow3( n ): # 1-е определение функции return n * n * n show( pow3, n ) pow3 = lambda n: n * n * n # 2-е определение функции с тем же именем show( pow3, n ) show( ( lambda n: n * n * n ), n ) # 3-е, использование анонимного описание функции
При вызове всех трёх объектов-функций мы получим один и тот же результат:
$ python func.py 1.3 <type 'function'> : <function pow3 at 0xb7662844> arg=1.3 => fun( arg )=2.197 <type 'function'> : <function <lambda> at 0xb7662bc4> arg=1.3 => fun( arg )=2.197 <type 'function'> : <function <lambda> at 0xb7662844> arg=1.3 => fun( arg )=2.197
Ещё более отчётливо это проявляется в Python версии 3, в которой всё является классами (в том числе, и
целочисленная переменная), а функции являются объектами программы, принадлежащими к классу
:
$ python3 func.py 1.3 <class 'function'> : <function pow3 at 0xb74542ac> arg=1.3 => fun( arg )=2.1970000000000005 <class 'function'> : <function <lambda> at 0xb745432c> arg=1.3 => fun( arg )=2.1970000000000005 <class 'function'> : <function <lambda> at 0xb74542ec> arg=1.3 => fun( arg )=2.1970000000000005
Примечание. Существуют ещё 2 типа объектов, допускающих функциональный вызов —
функциональный метод класса и функтор, о которых мы поговорим позже.
Если функциональные объекты Python являются такими же объектами, как и другие объекты данных, значит,
с ними можно и делать всё то, что можно делать с любыми данными:
- динамически изменять в ходе выполнения;
- встраивать в более сложные структуры данных (коллекции);
- передавать в качестве параметров и возвращаемых значений и т.д.
На этом (манипуляции с функциональными объектами как с объектами данных) и базируется функциональное
программирование. Python, конечно, не является настоящим языком функционального программирования, так,
для полностью функционального программирования существуют специальные языки: Lisp, Planner, а из более
свежих: Scala, Haskell. Ocaml, … Но в Python можно «встраивать» приёмы функционального программирования в
общий поток императивного (командного) кода, например, использовать методы, заимствованные из
полноценных функциональных языков. Т.е. «сворачивать» отдельные фрагменты императивного кода (иногда
достаточно большого объёма) в функциональные выражения.
Временами спрашивают: «В чём преимущества функционального стиля написания отдельных фрагментов
для программиста?». Основным преимуществом функционального программирования является то, что после
однократной отладки такого фрагмента в нём при последующем многократном использовании не возникнут
ошибки за счёт побочных эффектов, связанных с присвоениями и конфликтом имён.
Достаточно часто при программировании на Python используют типичные конструкции из области
функционального программирования, например:
print ( )
В результате запуска получаем:
$ python funcp.py
Встроенные типы
Хоть вы и можете использовать стандартные типы в качестве аннотаций, много полезного сокрыто в модуле .
Optional
Если вы пометите переменную типом и попытаетесь присвоить ей , будет ошибка:
Для таких случаев предусмотрена в модуле typing аннотация с указанием конкретного типа
Обратите внимание, тип опциональной переменной указывается в квадратных скобках
Any
Иногда вы не хотите ограничивать возможные типы переменной
Например, если это действительно не важно, или если вы планируете сделать обработку разных типов самостоятельно. В этом случае, можно использовать аннотацию
На следующий код mypy не будет ругаться:
Может возникнуть вопрос, почему не использовать ? Однако в этом случае предполагается, что хоть передан может быть любой объект, обращаться с ним можно только как с экземпляром .
Union
Для случаев, когда необходимо допустить использование не любых типов, а только некоторых, можно использовать аннотацию с указанием списка типов в квадратных скобках.
Кстати, аннотация эквивалентна , хотя такая запись и не рекомендуется.
Коллекции
Механизм аннотаций типов поддерживает механизм дженериков (, подробнее во второй части статьи), которые позволяют специфицировать для контейнеров типы элементов, хранящихся в них.
Списки
Для того, чтобы указать, что переменная содержит список можно использовать тип list в качестве аннотации. Однако если хочется конкретизировать, какие элементы содержит список, он такая аннотация уже не подойдёт. Для этого есть . Аналогично тому, как мы указывали тип опциональной переменной, мы указываем тип элементов списка в квадратных скобках.
Предполагается, что список содержит неопределенное количество однотипных элементов. Но при этом нет ограничений на аннотацию элемента: можно использовать , , и другие. Если тип элемента не указан, предполагается, что это .
Кроме списка аналогичные аннотации есть для множеств: и .
Кортежи
Кортежи в отличие от списков часто используются для разнотипных элементов. Синтаксис похож с одним отличием: в квадратных скобках указывается тип каждого элемента кортежа по отдельности.
Если же планируется использовать кортеж аналогично списку: хранить неизвестное количество однотипных элементов, можно воспользоваться многоточием ().
Аннотация без указания типов элементов работает аналогично
Словари
Для словарей используется . Отдельно аннотируется тип ключа и тип значений:
Аналогично используются и
Результат выполнения функции
Для указания типа результата функции можно использовать любую аннотацию. Но есть несколько особенных случаев.
Если функция ничего не возвращает (например, как ), её результат всегда равен . Для аннотации так же используем .
Корректными вариантами завершения такой функции будут: явный возврат , возврат без указания значения и завершение без вызова .
Если же функция никогда не возвращает управление (например, как ), следует использовать аннотацию :
Если это генераторная функция, то есть её тело содержит оператор , для возвращаемого можно воспользоватьтся аннотацией , либо :
Проверяет, что все элементы в последовательности True.
Описание:
Функция возвращает значение , если все элементы в итерируемом объекте — истинны, в противном случае она возвращает значение .
Если передаваемая последовательность пуста, то функция также возвращает .
Функция применяется для проверки на ВСЕХ значений в последовательности и эквивалентна следующему коду:
def all(iterable): for element in iterable if not element return False return True
Так же смотрите встроенную функцию
В основном функция применяется в сочетании с оператором ветвления программы . Работу функции можно сравнить с оператором в Python, только работает с последовательностями:
>>> True and True and True # True >>> True and False and True # False >>> all() # True >>> all() # False
Но между и в Python есть два основных различия:
- Синтаксис.
- Возвращаемое значение.
Функция всегда возвращает или (значение )
>>> all() # True >>> all(]) # False
Если в выражении все значения , то оператор возвращает ПЕРВОЕ истинное значение, а если все значения , то последнее ложное значение. А если в выражении присутствует значение , то ПЕРВОЕ ложное значение. Что бы добиться поведения как у функции , необходимо выражение с оператором обернуть в функцию .
>>> 3 and 1 and 2 and 6 # 6 >>> 3 and and 3 and [] # 0 >>> bool(3 and 1 and 2 and 6) # True >>> bool(3 and and 3 and []) # False
Из всего сказанного можно сделать вывод, что для успешного использования функции необходимо в нее передавать последовательность, полученную в результате каких то вычислений/сравнений, элементы которого будут оцениваться как или . Это можно достичь применяя функцию или выражения-генераторы списков, используя в них встроенные функции или методы, возвращающие значения, операции сравнения, оператор вхождения и оператор идентичности .
num = 1, 2.0, 3.1, 4, 5, 6, 7.9 # использование встроенных функций или # методов на примере 'isdigit()' >>> str(x).isdigit() for x in num # # использование операции сравнения >>> x > 4 for x in num # # использование оператора вхождения `in` >>> '.' in str(x) for x in num # # использование оператора идентичности `is` >>> type(x) is int for x in num # # использование функции map() >>> list(map(lambda x x > 1, num)) False, True, True, True, True, True, True
Примеры проводимых проверок функцией .
Допустим, у нас есть список чисел и для дальнейших операций с этой последовательностью необходимо знать, что все числа например положительные.
>>> num1 = range(1, 9) >>> num2 = range(-1, 7) >>> all() # True >>> all() # False
Или проверить, что последовательность чисел содержит только ЦЕЛЫЕ числа.
>>> num1 = 1, 2, 3, 4, 5, 6, 7 >>> num2 = 1, 2.0, 3.1, 4, 5, 6, 7.9 >>> all() # True >>> all() # False
Или есть строка с числами, записанными через запятую и нам необходимо убедится, что в строке действительно записаны только цифры. Для этого, сначала надо разбить строку на список строк по разделителю и проверить каждый элемент полученного списка на десятичное число методом . Что бы учесть правила записи десятичных чисел будем убирать точку перед проверкой строки на десятичное число.
>>> line1 = "1, 2, 3, 9.9, 15.1, 7" >>> line2 = "1, 2, 3, 9.9, 15.1, 7, девять" >>> all() # True >>> all() # False
Еще пример со строкой. Допустим нам необходимо узнать, есть ли в строке наличие открытой И закрытой скобки?
Декоратор функции в Python
Конструктивно речь идёт о некоторой функции, в качестве аргумента которого выступает другая функция. Декоратор в Python добавляет дополнительный функционал к функции, не меняя её содержимое.
Создание
Представьте, что мы имеем пару простых функций в Python:
def first_test(): print("Test function 1") def second_test(): print("Test function 2")
При этом мы желаем их дополнить таким образом, чтобы перед вызовом основного кода нашей функции печаталась строчка “Start function”, а в конце – строка “Stop function”.
Реализовать поставленную задачу можно несколькими методами. Во-первых, мы можем добавить необходимые строки в конец и начало каждой функции. Но вряд ли это удобно, ведь если мы пожелаем их убрать, придётся модифицировать тело функции.
Теперь поговорим о втором пути. Для начала создадим функцию:
def simple_decore(fn): def wrapper(): print("Start function") fn() print("Stop function") return wrapper
Теперь нужно обернуть функции в оболочку:
first_test_wrapped = simple_decore(first_test) second_test_wrapped = simple_decore(second_test)
Обратите внимание, что функции first_test и second_test не поменялись
>>> first_test() Test function 1 >>> second_test() Test function 2
Наши функции second_test_wrapped и first_test_wrapped обладают функционалом, который нам и нужен.
>>> first_test_wrapped() Start function Test function 1 Stop function >>> first_test_wrapped() Start function Test function 1 Stop function
Теперь, если надо, чтобы так работали функции с именами first_test и second_test, делаем следующее:
first_test = first_test_wrapped second_test = second_test_wrapped
Проверяем:
>>> first_test() Start function Test function 1 Stop function >>> second_test() Start function Test function 2 Stop function
Выполненные нами действия и являются реализацией идеи декоратора.
Правда, вместо строк:
def first_test(): print("Test function 1") first_test_wrapped = simple_decore(first_test) first_test = first_test_wrapped
мы можем написать иначе:
@simple_decore def first_test(): print("Test function 1")
В нашем случае @simple_decore – это не что иное, как декоратор функции.
Передаём аргументы в функцию с помощью декоратора
Бывает, что функция требует наличие аргумента, поэтому мы можем передать его через декоратор. Давайте создадим декоратор, принимающий аргумент и выводящий информацию о декорируемой нами функции и её аргументе.
def param_transfer(fn): def wrapper(arg): print("Start function: " + str(fn.__name__) + "(), with param: " + str(arg)) fn(arg) return wrapper
Чтобы продемонстрировать работу, создадим функцию, выводящую квадратный корень переданного ей числа, а в качестве декоратора, укажем созданный param_transfer:
@param_transfer def print_sqrt(num): print(num**0.5)
Теперь давайте выполним данную функцию с аргументом 4:
>>> print_sqrt(4) Start function print_sqrt(), with param 4 2.0
Декораторы для методов класса в Python
С декоратором можно объявлять и методы классов. Давайте выполним модификацию декоратора param_transfer:
def method_decor(fn): def wrapper(self): print("Start method: " + str(fn.__name__)) fn(self) return wrapper
Теперь приступим к созданию класса для представления 2-мерного вектора (математического). В классе определим метод norm(), выводящий модуль вектора.
class Vector(): def __init__(self, px = , py = ): self.px = px self.py = py @method_decor def norm(self): print((self.px**2 + self.py**2)**0.5)
Что же, осталось продемонстрировать работу нашего метода:
>>> vc = Vector(px=10, py=5) >>> vc.norm() Start method norm 11.180339887498949
Возвращаем результат работы функции через декоратор
Зачастую создаваемые функции выполняют возвращение какого-либо значения. Чтобы это стало возможным осуществить через декоратор, нужно специальным образом построить нашу внутреннюю функцию:
def decor_with_return(fn): def wrapper(*args, **kwargs): print("Start method: " + str(fn.__name__)) return fn(*args, **kwargs) return wrapper
Возможно применение этого декоратора и для оборачивания функций, принимающих различные аргументы и возвращающие значение:
@decor_with_return def calc_sqrt(val): return val**0.5
Осталось выполнить функцию calc_sqrt() с параметром 16:
>>> tmp = calc_sqrt(16) Start method calc_sqrt >>> print(tmp)