Функции представляют собой средство, дающее возможность упаковывать и параметризовать функциональность. В языке Python можно создать четыре типа функций: глобальные функции, локальные функции, лямбда-функции и методы.
Все функции, которые мы создавали до сих пор, являются глобальными
функциями. Глобальные объекты (включая функции) доступны из любой
точки программного кода в том же модуле (то есть в том же самом файле
.py
), которому принадлежит объект. Глобальные объекты доступны
также и из других модулей, как будет показано в следующей главе.
Локальные функции (их еще называют вложенными функциями) – это функции, которые объявляются внутри других функций. Эти функции видимы только внутри тех функций, где они были объявлены – они особенно удобны для создания небольших вспомогательных функций, которые нигде больше не используются.
Лямбда-функции – это выражения, поэтому они могут создаваться непосредственно в месте их использования; они имеют множество ограничений по сравнению с обычными функциями. Методы – это те же функции, которые ассоциированы с определенным типом данных и могут использоваться только в связке с этим типом данных.
Синтаксис создания функции (глобальной или локальной) имеет следующий вид:
def functionName(parameters):
suite
Параметры parameters
являются необязательными и при наличии более
одного параметра записываются как последовательность идентификаторов
через запятую или в виде последовательности пар identifier=value
, о
чем вскоре будет говориться подробнее. Например, ниже приводится
функция, которая вычисляет площадь треугольника по формуле Герона:
def heron(a, b, c):
s = (a + b + c) / 2
return math.sqrt(s * (s - a) * (s - b) * (s - c))
Внутри функции каждый параметр, a
, b
и c
, инициализируется
соответствующими значениями, переданными в виде аргументов. При вызове
функции мы должны указать все аргументы, например, heron(3, 4, 5)
.
Если передать слишком мало или слишком много аргументов, будет
возбуждено исключение TypeError
. Производя такой вызов, мы
говорим, что используем позиционные аргументы, потому что каждый
переданный аргумент становится значением параметра в соответствующей
позиции. То есть в данном случае при вызове функции параметр a
получит значение 3
, параметр b
– значение 4
и параметр с
–
значение 5
.
Все функции в языке Python возвращают какое-либо значение, хотя
вполне возможно (и часто так и делается) просто игнорировать это
значение. Возвращаемое значение может быть единственным значением или
кортежем значений, а сами значения могут быть коллекциями,
поэтому практически не существует никаких ограничений на то, что
могут возвращать функции. Мы можем покинуть функцию в любой
момент, используя инструкцию return
. Если инструкция return
используется без аргументов или если мы вообще не используем
инструкцию return
, функция будет возвращать значение None
.
Некоторые функции имеют параметры, для которых может существо- вать вполне разумное значение по умолчанию. Например, ниже при- водится функция, которая подсчитывает количество алфавитных сим- волов в строке; по умолчанию подразумеваются алфавитные символы из набора ASCII:
def letter_count(text, letters=string.ascii_letters):
letters = frozenset(letters)
count = 0
for char in text:
if char in letters:
count += 1
return count
Здесь при помощи синтаксиса parameter=default
было определено
значение по умолчанию для параметра letters
. Это позволяет вызывать
функцию letter_count()
с единственным аргументом, например,
letter_count("Maggie and Hopey")
. В этом случае внутри функции
параметр letter
будет содержать строку, которая была задана как
значение по умолчанию. Но за нами сохраняется возможность изменить
значение по умолчанию, например, указав дополнительный позиционный
аргумент: letter_count("Maggie and Hopey", "aeiouAEIOU")
, или
используя именованный аргумент (об именованных аргументах
рассказывается ниже): letter_count("Maggie and Hopey", letters="aeiouAEIOU")
.
Синтаксис параметров не позволяет указывать параметры, не имеющие
значений по умолчанию, после параметров со значениями по умолчанию,
поэтому такое определение: def bad(a, b=1, c):
, будет вызывать
синтаксическую ошибку. С другой стороны, мы не обязаны передавать
аргументы в том порядке, в каком они указаны в определении функции –
мы можем использовать именованные аргументы и передавать их в виде
name=value
.
В предыдущей главе мы видели, что для передачи позиционных аргументов
можно использовать оператор распаковывания последовательностей
(*
). Например, если возникает необходимость вычислить площадь
треугольника, а длины всех его сторон хранятся в списке, то мы могли
бы вызвать функцию так: heron(sides[0], sides[1], sides[2])
, или
просто распаковать список и сделать вызов намного проще:
heron(*sides)
. Если элементов в списке (или в другой
последовательности) больше, чем параметров в функции, мы можем
воспользоваться операцией извлечения среза, чтобы извлечь нужное число
аргументов.
Мы можем также использовать оператор распаковывания последовательности
в списке параметров функции. Это удобно, когда необходимо создать
функцию, которая может принимать переменное число позиционных
аргументов. Ниже приводится функция product()
, которая
вычисляет произведение своих аргументов:
def product(*args):
result = 1
for arg in args:
result *= arg
return result
Эта функция имеет единственный аргумент с именем args
. Наличие
символа *
перед ним означает, что внутри функции параметр args
обретает форму кортежа, значениями элементов которого будут значения
переданных аргументов. Ниже приводятся несколько примеров вызова
функции:
product(1, 2, 3, 4) # args == (1, 2, 3, 4); вернет: 24
product(5, 3, 8) # args == (5, 3, 8); вернет: 120
product(11) # args == (11,); вернет: 11
Мы можем использовать именованные аргументы вслед за позиционными, как в функции, которая приводится ниже, вычисляющей сумму своих аргументов, каждый из которых возводится в заданную степень:
def sum_of_powers(*args, power=1):
result = 0
for arg in args:
result += arg ** power
return result
Эта функция может вызываться только с позиционными аргументами,
например: sum_of_powers(1, 3, 5)
, или как с позиционными, так и с
именованным аргументами, например: sum_of_powers(1, 3, 5, power=2)
.
Допускается также использовать символ *
в качестве самостоятельного
«параметра». В данном случае он указывает, что после символа *
не
может быть других позиционных параметров, однако указание именованных
аргументов допускается. Ниже приводится модифицированная версия
функции heron()
. На этот раз функция принимает точно три позиционных
аргумента и один необязательный именованный аргумент.
def heron2(a, b, c, *, units="meters"):
s = (a + b + c) / 2
area = math.sqrt(s * (s - a) * (s - b) * (s - c))
return "{0} {1}".format(area, units)
Ниже приводятся несколько примеров вызовов функции:
heron2(25, 24, 7) # вернет: '84.0 meters'
heron2(41, 9, 40, units="inches") # вернет: '180.0 inches'
heron2(25, 24, 7, "inches") # ОШИБКА! Возбудит исключение TypeError
В третьем вызове мы попытались передать четыре позиционных аргумента,
но оператор *
не позволяет этого и вызывает исключение TypeError
.
Поместив оператор *
первым в списке параметров, мы тем самым
полностью запретим использование любых позиционных аргументов и
вынудим тех, кто будет вызывать ее, использовать именованные
аргументы. Ниже приводится пример сигнатуры такой (вымышленной)
функции:
def print_setup(*, paper="Letter", copies=1, color=False):
Мы можем вызывать функцию print_setup()
без аргументов, допуская
использование значений по умолчанию. Или изменить некоторые или
все значения по умолчанию, например:
print_setup(paper="A4", color=True)
. Но если мы попытаемся
использовать позиционные аргументы, например: print_setup("A4")
,
будет возбуждено исключение TypeError
.
Так же, как мы распаковываем последовательности для заполнения
позиционных параметров, можно распаковывать и отображения –
с помощью оператора распаковывания отображений (**
). Мы можем
использовать оператор **
, чтобы передать содержимое словаря в
функцию print_setup()
. Например:
options = dict(paper="A4", color=True)
print_setup(**options)
В данном случае пары «ключ-значение» словаря options
будут
распакованы, и каждое значение будет ассоциировано с параметром, чье
имя соответствует ключу этого значения. Если в словаре обнаружится
ключ, не совпадающий ни с одним именем параметра, будет возбуждено
исключение TypeError
. Любые аргументы, для которых в словаре не
найдется соответствующего элемента, получат значение по умолчанию, но
если такие аргументы не имеют значения по умолчанию, будет возбуждено
исключение TypeError
.
Кроме того, имеется возможность использовать оператор распаковывания
вместе с параметрами в объявлении функции. Это позволяет создавать
функции, способные принимать любое число именованных аргументов. Ниже
приводится функция add_person_details()
, которая принимает номер
карточки социального страхования и фамилию в виде позиционных
аргументов, а также произвольное число именованных аргументов:
def add_person_details(ssn, surname, **kwargs):
print("SSN =", ssn)
print(" surname =", surname)
for key in sorted(kwargs):
print(" {0} = {1}".format(key, kwargs[key]))
Эта функция может вызываться как только с двумя позиционными
аргументами, так и с дополнительной информацией, например:
add_person_details(83272171, "Luther", forename="Lexis", age=47)
.
Такая возможность обеспечивает огромную гибкость. Конечно, мы можем
также одновременно принимать переменное число позиционных аргументов и
переменное число именованных аргументов:
def print_args(*args, **kwargs):
for i, arg in enumerate(args):
print("positional argument {0} = {1}".format(i, arg))
for key in kwargs:
print("keyword argument {0} = {1}".format(key, kwargs[key]))
Эта функция просто выводит полученные аргументы. Она может вызываться вообще без аргументов или с произвольным числом позиционных и именованных аргументов.
Иногда бывает удобно иметь несколько глобальных переменных, доступных из разных функций программы. В этом нет ничего плохого, если речь идет о «константах», но в случае переменных – это не самый лучший выход, хотя для коротких одноразовых программ это в некоторых случаях можно считать допустимым.
Программа digit_names.py принимает
необязательный код языка (en
или ru
) и число в виде аргументов
командной строки и выводит названия всех цифр заданного числа. То есть
если в командной строке программе было передано число 123
, она
выведет one two three
. В программе имеется три глобальные
переменные:
Language = "en"
ENGLISH = {0: "zero", 1: "one", 2: "two", 3: "three", 4: "four",
5: "five", 6: "six", 7: "seven", 8: "eight", 9: "nine"}
RUSSIAN = {0: "ноль", 1: "один", 2: "два", 3: "три", 4: "четыре",
5: "пять", 6: "шесть", 7: "семь", 8: "восемь", 9: "девять"}
Мы следуем соглашению, в соответствии с которым имена переменных, играющих роль констант, записываются только символами верхнего регистра, и установили английский язык по умолчанию.
В некотором другом месте программы выполняется обращение к переменной
Language
, и ее значение используется при выборе соответствующего
словаря:
def print_digits(digits):
dictionary = ENGLISH if Language == "en" else RUSSIAN
for digit in digits:
print(dictionary[int(digit)], end=" ")
print()
Когда интерпретатор Python встречает имя переменной Language
внутри
функции, он пытается отыскать его в локальной области видимости (в
области видимости функции) и не находит. Поэтому он продолжает поиск в
глобальной области видимости (в области видимости файла .py
), где и
обнаруживает его.
Ниже приводится содержимое функции main()
программы. Она изменяет
значение переменной Language
в случае необходимости и вызывает
функцию print_digits()
для вывода результата.
def main():
if len(sys.argv) == 1 or sys.argv[1] in {"-h", "--help"}:
print("usage: {0} [en|ru] number".format(sys.argv[0]))
sys.exit()
args = sys.argv[1:]
if args[0] in {"en", "ru"}:
global Language
Language = args.pop(0)
print_digits(args.pop(0))
Обратите внимание на использование инструкции global
в этой
функции. Эта инструкция используется для того, чтобы сообщить
интерпретатору, что данная переменная существует в глобальной области
видимости (в области видимости файла .py
) и что операция
присваивания должна применяться к глобальной переменной; без этой
инструкции операция присваивания создаст локальную переменную с тем же
именем.
Если не использовать инструкцию global
, программа сохранит
свою работоспособность, но когда интерпретатор встретит переменную
Language
в условной инструкции if
, он попытается отыскать ее в
локальной области видимости (в области видимости функции) и, не
обнаружив ее, создаст новую локальную переменную с именем Language
,
оставив глобальную переменную Language
без изменений. Эта
малозаметная ошибка будет проявляться только в случае запуска
программы с аргументом ru
, потому что в этом случае будет создана
новая локальная переменная Language
, в которую будет записано
значение ru
, а глобальная переменная Language
, которая
используется функцией print_digits()
, по-прежнему будет иметь
значение en
.
В сложных программах лучше вообще не использовать глобальные
переменные, за исключением констант, которые не требуют употребления
инструкции global
.
Лямбда-функции – это функции, для создания которых используется следующий синтаксис:
lambda parameters: expression
Часть parameters
является необязательной, а если она
присутствует, то обычно представляет собой простой
список имен переменных, разделенных запятыми, то
есть позиционных аргументов, хотя при необходимости
допускается использовать полный синтаксис определе-
ния аргументов, используемый в инструкции def
. Выражение
expression
не может содержать условных инструкций или циклов (хотя
условные выражения являются допустимыми), а также не может содержать
инструкцию return
(или yield
). Результатом лямбда-выражения
является анонимная функция. Когда вызывается лямбда-функция, она
возвращает результат вычисления выражения expression
. Если выражение
expression представляет собой кортеж, оно должно быть заключено в
круглые скобки.
Ниже приводится пример простой лямбда-функции, которая добавляет (или
не добавляет) суффикс s
в зависимости от того, имеет ли аргумент
значение 1:
s = lambda x: "" if x == 1 else "s"
Лямбда-выражение возвращает анонимную функцию, которая присваивается
переменной s
. Любая (вызываемая) переменная может вызываться как
функция при помощи круглых скобок, поэтому после выполнения некоторой
операции можно при помощи функции s()
вывести сообщение с числом
обработанных файлов, например:
print("{0} file{1} processed".format(count, s(count)))
Лямбда-функции часто используются в виде аргумента key
встроенной
функции sorted()
или метода list.sort()
. Предположим, что имеется
список, элементами которого являются трехэлементные кортежи (номер
группы, порядковый номер, название), и нам необходимо отсортировать
этот список различными способами. Ниже приводится пример такого
списка:
elements = [(2, 12, "Mg"), (1, 11, "Na"), (1, 3, "Li"), (2, 4, "Be")]
Отсортировав список, мы получим следующий результат:
[(1, 3, 'Li'), (1, 11, 'Na'), (2, 4, 'Be'), (2, 12, 'Mg')]
Ранее, когда мы рассматривали функцию sorted()
, то видели, что
имеется возможность изменить порядок сортировки, если в аргументе key
передать требуемую функцию. Например, если необходимо отсортировать
список не по естественному порядку: номер группы, порядковый номер и
название, а по порядковому номеру и названию, то мы могли бы написать
маленькую функцию def ignore0(e): return e[1], e[2]
и передавать ее
в аргументе key
. Но создавать в программе массу крошечных функций,
подобных этой, очень неудобно, поэтому часто используется
альтернативный подход, основанный на применении лямбда-функций:
elements.sort(key=lambda e: (e[1], e[2]))
Здесь в качестве значения аргумента key используется выражение
lambda e: (e[1], e[2])
, которому в виде аргумента e последовательно
передаются все трехэлементные кортежи из списка. Круглые скобки,
окружающие лямбда-выражение, обязательны, когда выражение является
кортежем и лямбда-функция создается как аргумент другой функции. Для
достижения того же эффекта можно было бы использовать операцию
извлечения среза:
elements.sort(key=lambda e: e[1:3])
Немного более сложная версия обеспечивает возможность сортировки по названию, без учета регистра символов, и порядковому номеру:
elements.sort(key=lambda e: (e[2].lower(), e[1]))
Что произойдет, если функция получит аргументы, имеющие ошибочные значения? Что случится, если в реализации алгоритма будет допущена ошибка и вычисления будут выполнены неправильно? Самое неприятное, что может произойти, – это то, что программа будет выполняться без каких-либо видимых проблем, но будет давать неверные результаты. Один из способов избежать таких коварных проблем состоит в том, чтобы писать тесты. Другой способ состоит в том, чтобы определить предварительные условия и ожидаемый конечный результат, и сообщать об ошибке, если они не соответствуют друг другу. В идеале следует использовать как тестирование, так и метод на основе сравнения предварительных условий и ожидаемых результатов.
Предварительные условия и ожидаемый результат можно задать с помощью
инструкции assert
, которая имеет следующий синтаксис:
assert boolean_expression, optional_expression
Если выражение boolean_expression
возвращает значение False
,
возбуждается исключение AssertionError
. Если задано необязательное
выражение optional_expression
, оно будет использовано в качестве
аргумента исключения AssertionError
, что удобно для передачи сообщений
об ошибках. Однако следует отметить, что утверждения предназначены
для использования разработчиками, а не конечными пользователями.
Проблемы, возникающие в процессе нормальной эксплуатации программы,
такие как отсутствующие файлы или ошибочные аргументы командной
строки, должны обрабатываться другими средствами, например,
посредством вывода сообщений об ошибках или записи сообщений в файл
журнала.