Функции в Python
- Понятие функции в Python
- Создание и вызов функций
- Аргументы функций
- Области видимости в Python
- lambda-выражения или анонимные функции
- Рекурсивные функции
- Декораторы функций
- Атрибуты функций, аннотации и документирование
- Концепции проектирования функций
- Краткие итоги параграфа
- Вопросы и задания для самоконтроля
Понятие функции в Python
Несмотря на то, что формально функции мы еще не рассматривали, некоторое понятие о них мы уже имеем. Ведь ранее мы не только использовали готовые встроенные функции и различные методы типов, но еще и определяли простейшие собственные функции. Теперь же настало время рассмотреть этот вопрос более подробно. Итак.
Функция в Python (от англ. function) – это блок программного кода на языке Python, который определяется один раз и далее может быть использован многократно.
Каждая хорошо спроектированная функция по сути решает какую-то одну конкретную задачу, что дает возможность программистам разбивать сложную систему на достаточно простые и легко управляемые части. При этом достаточно всего лишь один раз определить функцию и далее ее можно будет использовать практически в любом месте программы, зачастую заменяя довольно объемные избыточные копии блоков одного и того же программного кода вызовами созданной ранее функции. Как следствие, при необходимости внесения изменений в код, делать это придется уже не во многих местах программы, а только в одном – в теле самой функции (см. пример №1).
Таким образом функции можно смело назвать основными программными структурами языка Python, которые позволяют существенно уменьшить время и трудозатраты на разработку приложений, а также их дальнейшее сопровождение за счет следующих преимуществ:
- дают возможность разбивать сложную систему на небольшие и легко управляемые части, каждая из которых может разрабатываться отдельно,
- обеспечивают многократное использование программного кода,
- уменьшают его избыточность,
- повышают читабельность исходного кода,
- упрощают его редактирование.
Создание и вызов функций в Python
Для создания функций в Python предназначена инструкция def, которая создает объект функции и связывает его с именем. В общем виде инструкция имеет следующий формат:
def <Имя функции>(arg_1, arg_2, ..., arg_n):
<Тело функции>
return <Возвращаемое значение>
Как видим, строка заголовка инструкции определения функции начинается с ключевого слова def, затем через пробел указывается имя функции и следующие за ним обязательные круглые скобки, внутри которых через запятую перечисляются передаваемые функции аргументы (их еще называют параметрами). Можно определять функции и без аргументов, но круглые скобки при этом все равно нужно указывать. Завершается строка заголовка обязательным двоеточием. Далее следует тело функции, представляющее собой обычный блок программного кода, который будет выполняться каждый раз при вызове функции. При этом, если тело функции состоит из одной простой инструкции, его можно записывать вслед за двоеточием в строке заголовка. В более сложных случаях инструкции тела функции записываются с отступами в соответствии с общепринятыми правилами.
Помимо обычных инструкций в любом месте тела функции может содержаться необязательная инструкция return, которая предназначена для выхода из функции и возврата результата вызывающей программе в месте вызова функции. В случае отсутствия инструкции return функция будет завершать свою работу по достижении потоком управления конца тела функции. Однако технически функция все равно будет неявно возвращать результат в виде объекта None, который обычно просто игнорируется.
Также для использования в функциях предназначена специальная инструкция yield, которая превращает обычную функцию в функцию-генератор. Однако здесь мы заострять внимание на ней не будем, поскольку данный вопрос был подробно рассмотрен нами в предыдущем параграфе.
После того, как функция будет определена, ее можно начинать использовать, многократно вызывая практически в любое время и в любом месте скрипта. Осуществляются такие вызовы при помощи имени функции и круглых скобок с передаваемыми в функцию значениями аргументов, которые также должны перечисляться через запятую (см. пример №1).
# Функция определяется один раз.
def pow_func(x, y):
# Возводим x в степень y.
p = x**y
# Возвращаем результат вызывающей программе.
return p
# После создания функцию можно вызывать.
res_1 = pow_func(3, 3)
# Получим 27, т.е. 3 в кубе.
print('pow_func(3, 3) ->', res_1, end='\n\n')
# При этом вызовы можно осуществлять многократно.
res_2 = pow_func(4, 2)
# Выведет 16, т.е. 4 в квадрате.
print('pow_func(4,2) ->', res_2)
pow_func(3, 3) -> 27
pow_func(4,2) -> 16
Пример №1. Создание и вызов функций в Python (часть 1).
Следует иметь в виду, что в отличие от некоторых других языков программирования в Python функции становятся доступными для использования только после того, как до них дойдет поток выполнения программы. Если попытаться вызвать функцию до ее определения в коде программы, интерпретатор возбудит исключение. При этом создавать функции можно практически везде, где могут появляться инструкции, включая условные инструкции и определения других функций (см. пример №2).
# Вызывать ф-цию можно только после определения.
# local variable func_1 referenced before assignment.
# func_1()
# Я ввел 1 (попробуйте ввести, например, -1).
n = int(input())
# Определяем ф-цию в зависимости от условия.
if n >= 0:
# Определяем 1-ю функцию.
def func_1(): print('func_1 определена!')
else:
# Определяем 2-ю функцию.
def func_2(): print('func_2 определена!')
# Вызов разрешен, т.к. func_1 уже определена.
# Выведет 'func_1 определена!'.
func_1()
# Вызов запрещен, т.к. func_2 определена не была.
# local variable 'func_2' referenced before assignment.
func_2()
func_1 определена!
local variable 'func_2' referenced before assignment
Пример №2. Создание и вызов функций в Python (часть 2).
Аргументы функций в Python
Как было сказано выше, в случаях, когда функция определяется с аргументами, их имена перечисляются через запятую в круглых скобках после имени определяемой функции. Далее, в моменты вызовов функции, все определенные имена автоматически становятся ее локальными переменными, которым присваиваются переданные в функцию значения. При этом по умолчанию сопоставление аргументов производится слева направо в соответствии с их позициями, а самих аргументов допускается передавать ровно столько, сколько имен было указано в заголовке функции при ее определении (см. пример №3).
# Определяем и вызываем функцию без аргументов.
def func_1(): print('Ok')
# Выведет 'Ok'.
func_1()
# ...func_1() takes 0 positional arguments but 1 was given.
# func_1(57)
# Функция с тремя позиционными аргументами.
def func_2(a, b, c): return a + b + c
# При вызове должны быть переданы все 3 аргумента.
print(func_2(2, 33, 87))
# ...func_2() missing 1 required positional argument: c.
# func_2(4, 8)
Ok
122
Пример №3. Передача аргументов функциям (часть 1).
Как видим, несоответствие количества передаваемых аргументов при вызове функции с количеством указанных имен аргументов при ее определении однозначно приводит к ошибке. Поэтому в ситуациях, когда точное количество передаваемых аргументов заранее неизвестно, следует определять и вызывать функции, используя один из доступных в Python специальных режимов сопоставления аргументов, которые мы сейчас и перечислим.
- def func(arg_1, arg_2, ...): – рассмотренный нами стандартный случай определения функции. Во время вызова такая функция ожидает получить строго определенное количество аргументов, сопоставление которых будет происходить либо по их позициям, либо по именам. При этом в случае использования в вызове функции именованных аргументов их разрешается указывать в любом порядке, но только после того, как будут переданы все имеющиеся позиционные аргументы, которые в любом случае будут сопоставляться как и положено, т.е. слева направо в соответствии с их позициями (см. пример №4).
# Определяем функцию с тремя позиционными аргументами.
def func(x, y, z): return x/y + z
# Передаем три позиционных аргумента.
# Выведет func(8, 2, 5) -> 9.0.
print('func(8, 2, 5) ->', func(8, 2, 5))
# Для некоторых арг-тов указываем имена, но сперва в любом
# случае должны идти позиционные арг-ты. Выведет 9.0.
print('func(x=8, y=2, z=5) ->', func(x=8, y=2, z=5))
print('func(y=2, x=8, z=5) ->', func(y=2, x=8, z=5))
print('func(z=5, x=8, y=2) ->', func(z=5, x=8, y=2))
print('func(8, y=2, z=5) ->', func(8, y=2, z=5))
print('func(8, z=5, y=2) ->', func(8, z=5, y=2))
print('func(8, 2, z=5) ->', func(8, 2, z=5))
# Именованные аргументы разрешается передавать только
# после позиционных. Так что везде получаем ошибки.
# print(func(x=8, 2, 5))
# print(func(8, y=2, 5))
# print(func(x=8, 2, z=5))
func(8, 2, 5) -> 9.0
func(x=8, y=2, z=5) -> 9.0
func(y=2, x=8, z=5) -> 9.0
func(z=5, x=8, y=2) -> 9.0
func(8, y=2, z=5) -> 9.0
func(8, z=5, y=2) -> 9.0
func(8, 2, z=5) -> 9.0
Пример №4. Стандартная передача аргументов функциям.
- def func(arg_1, arg_2, ..., arg_1=val_1, arg_2=val_2, ...) – эта форма записи используется для определения функций, у которых предусмотрены аргументы со значениями по умолчанию. Перечисляются такие аргументы после обычных позиционных аргументов (если они имеются), а их наличие позволяет вызывать функцию без передачи им значений, т.к. в таком случае будут просто использованы значения по умолчанию. Во всем остальном для таких функций справедливы правила стандартного режима передачи аргументов (см. пример №5).
# Укажем для одного аргумента значение по умолчанию.
def func(x, y=1): return x/y
# Везде получим 4.0, т.к. 8/2 = 4.0.
print('func(8, 2) ->', func(8, 2))
print('func(x=8, y=2) ->', func(x=8, y=2))
print('func(8, y=2) ->', func(8, y=2), end='\n\n')
# Ошибка, т.к. первыми должны передаваться
# позиционные аргументы и только потом именованные.
# print('func(x=8, 2) ->', func(x=8, 2))
# Аргумент со значением по умолчанию можно не указывать.
# Везде получим 2.0, т.к. 2/1 = 2.0.
print('func(2) ->', func(2))
print('func(x=2) ->', func(x=2), end='\n\n')
# ...missing 1 required positional argument: x.
# print(func(y=2))
# print(func())
func(8, 2) -> 4.0
func(x=8, y=2) -> 4.0
func(8, y=2) -> 4.0
func(2) -> 2.0
func(x=2) -> 2.0
Пример №5. Передача аргументов со значениями по умолчанию.
- def func(arg_1, ..., arg_n, *args) – при вызовах функции, определенной таким способом, все дополнительные аргументы будут объединяться в кортеж с именем args. Если передаваемых аргументов будет меньше n, будет вызвана ошибка. Сама запись *args должна идти после перечисления всех позиционных аргументов, включая и позиционные аргументы со значениями по умолчанию (см. пример №6).
# 1-й аргумент будет присвоен x, а все
# остальные будут собраны в кортеж y.
def func(x, *y): return x + sum(y)
# Получим 5, т.к. 5 + sum(()) = 5.
print('func(5) ->', func(5))
# Получим 8, т.к. 5 + sum((3,)) = 8.
print('func(5, 3) ->', func(5, 3))
# Получим 15, т.к. 5 + sum((3, 7)) = 15.
print('func(5, 3, 7) ->', func(5, 3, 7))
# Ошибка, т.к. х не имеет значения по умолчанию.
# print(func())
func(5) -> 5
func(5, 3) -> 8
func(5, 3, 7) -> 15
Пример №6. Сбор аргументов функции в кортеж.
- def func(arg_1, ..., arg_n, *args, named_arg_1, ..., named_arg_m) – здесь также все дополнительные аргументы будут объединяться в кортеж с именем args, но при этом все аргументы named_arg_1, ..., named_arg_m должны будут передаваться функции только именованными. Если собирать аргументы в кортеж не требуется, но функция проектируется для именованных аргументов, разрешается использовать синтаксис def func(arg_1, ..., arg_n, *, named_arg_1, ..., named_arg_m), где все аргументы после звездочки, опять же, должны будут передаваться функции только именованными (см. пример №7).
# z определяем именованным аргументом.
def func(x, *y, z): return x + sum(y) + z
# Получим 15, т.к. 5 + sum((7,)) + 3 = 15.
print('func(5, 7, z=3) ->', func(5, 7, z=3))
# Получим ошибку, т.к. именованные аргументы
# должны передаваться только после позиционных!
# print(func(5, z=7, 3))
# ...missing 1 required keyword-only argument: 'z'.
print('func(5, 7, 3) ->', func(5, 7, 3))
func(5, 7, z=3) -> 15
main..func() missing 1 required keyword-only argument: 'z'
Пример №7. Определение функции с именованными аргументами.
- def func(arg_1, ..., arg_n, *args, named_arg_1, ..., named_arg_m, **named_args) – это наиболее общая форма определения функции с позиционными и именованными аргументами: при вызовах такой функции все дополнительные позиционные аргументы будут объединяться в кортеж с именем args, а все дополнительные именованные аргументы будут объединяться в словарь с именем named_args (см. пример №8).
# Собираем именованные аргументы в словарь.
def func(x, *, y, **z): print(x, y, z)
# Выведет 1 2 {'a': 3, 'b': 4}.
func(1, y=2, a=3, b=4)
# Выведет 1 2 {'z': 3, 'f': 4}.
func(1, y=2, z=3, f=4)
# Выведет 1 2 {}.
func(1, y=2)
1 2 {'a': 3, 'b': 4}
1 2 {'z': 3, 'f': 4}
1 2 {}
Пример №8. Сбор именованных аргументов в словарь.
Следует помнить, что форма записи arg=val в определении функции означает, что данный аргумент определяется со значением по умолчанию, а в вызове функции запись представляет собой передачу аргумента по имени. Кроме того, формы записей *args и **named_args также имеют разный смысл для определения и вызова функции: в первом случае они собирают аргументы в кортеж и словарь, а во втором случае указывают интерпретатору на необходимость распаковки последовательностей в отдельные аргументы и пары (см. пример №9).
# Собираем аргументы в последовательности.
def func(*x, **y): print(x, y)
# Вывело (1, 2) {'a': 1, 'b': 2}.
func(1, 2, a=1, b=2)
# А здесь последовательности будем распаковывать.
def func(x, y): print(x, y)
# Равносильно func(1, 2). Вывело 1, 2.
func(*[1, 2])
# Равносильно func(x=1, y=2). Вывело 1, 2.
func(**{'x': 1, 'y': 2})
# Ошибка, т.к. мы определили имена x и y.
# ...got an unexpected keyword argument a.
# func(**{'a': 1, 'b': 2})
(1, 2) {'a': 1, 'b': 2}
1 2
1 2
Пример №9. Передача аргументов функциям (часть 2).
При первом прочтении информация о возможных режимах сопоставления аргументов может показаться несколько запутанной, но это только на первый взгляд. На самом деле все оказывается значительно проще в особенности, если придерживаться пары простых правил:
- в заголовке определяемой функции аргументы должны указываться в следующем порядке: позиционные аргументы (arg_1, arg_2, ...), позиционные аргументы со значениями по умолчанию (arg_1=val_1, arg_2=val_2, ...), аргумент со звездочкой для сбора дополнительных позиционных аргументов в кортеж (*args), именованные аргументы (named_arg_1, named_arg_2, ...), именованные аргументы со значениями по умолчанию (named_arg_1=val_1, named_arg_2=val_2, ...) и аргумент с двумя звездочками для сбора дополнительных именованных аргументов в словарь (**named_args);
- при вызове функции порядок передачи аргументов должен быть следующим: обычные позиционные аргументы (arg_1, arg_2, ...), позиционные аргументы в форме *args (будут распакованы интерпретатором в значения arg_1, arg_2, ...), обычные именованные аргументы (named_arg_1=val_1, named_arg_2=val_2, ...) и в самом конце именованные аргументы в форме **named_args (будут распакованы интерпретатором в пары named_arg_1=val_1, named_arg_2=val_2, ...).
Области видимости в Python
Как мы знаем, все имена в Python становятся доступны для использования только после того, как им будет присвоено какое-нибудь значение. Так вот, место, где переменной присваивается значение, как раз и определяет область ее видимости. Но поскольку значения переменным могут быть присвоены в трех разных местах текущего модуля, то и областей видимости выделяют тоже три.
- Если присваивание переменной выполняется внутри инструкции def, переменная становится локальной для этой функции. Это касается как аргументов функции, так и переменных, созданных в теле функции. Локальные переменные доступны только внутри своей функции, т.е. в текущей локальной области видимости, и недоступны за ее пределами, включая другие локальные области видимости невложенных в нее функций.
- Если присваивание производится в пределах объемлющей инструкции def, переменная становится локальной для данной объемлющей функции и нелокальной для данной вложенной функции. В общем случае такая переменная будет доступна внутри любой вложенной функции при отсутствии в ней локальной переменной с таким же именем.
- Если присваивание производится на верхнем уровне модуля (файла скрипта) за пределами всех инструкций def, переменная становится глобальной для всего файла. В общем случае глобальная переменная будет доступна внутри любой функции текущего модуля при отсутствии в ней локальной переменной с таким же именем (см. пример №10).
# Инициализировали глобальную переменную.
glob_var = 'glob_var'
# Выведет glob_var in global.
print(glob_var, 'in global', end='\n\n')
# Объявляем объемлющую функцию.
def func_1(loc_var):
# Локальная переменная для func_1.
loc_var_1 = 'loc_var_1'
# Выведет glob_var in func_1.
print(glob_var, 'in func_1')
# Выведет loc_var in func_1.
print(loc_var, 'in func_1')
# Выведет loc_var_1 in func_1.
print(loc_var_1, 'in func_1', end='\n\n')
# Объявляем вложенную функцию.
def func_2():
# Локальная переменная для func_2.
loc_var_2 = 'loc_var_2'
# Выведет glob_var in func_2.
print(glob_var, 'in func_2')
# Выведет loc_var_2 in func_2.
print(loc_var_2, 'in func_2')
# Выводим нелокальные для func_2.
# Выведет loc_var in func_2.
print(loc_var, 'in func_2')
# Выведет loc_var_1 in func_2.
print(loc_var_1, 'in func_2')
# Вызываем вложенную функцию func_2.
func_2()
# Вызываем объемлющую функцию.
func_1('loc_var')
glob_var in global
glob_var in func_1
loc_var in func_1
loc_var_1 in func_1
glob_var in func_2
loc_var in func_2
loc_var_1 in func_2
loc_var_2 in func_2
Пример №10. Области видимости в Python (часть 1).
Как видим, глобальная переменная glob_var доступна и в объемлющей функции, и во вложенной. Точно также локальная для функции func_1 переменная loc_var доступна в локальной области вложенной функции func_2 (для нее она является нелокальной переменной). Но если мы попробуем использовать локальные переменные в объемлющей или глобальной области видимости, то наверняка получим ошибку, т.к. локальные переменные доступны только в своей области видимости (см. пример №11).
# Объявляем объемлющую функцию.
def func_1():
# Локальная переменная для func_1.
loc_var_1 = 'loc_var_1'
# Выведет loc_var_1 in func_1.
print(loc_var_1, 'in func_1')
# name 'loc_var_2' is not defined.
# print(loc_var_2)
# Объявляем вложенную функцию.
def func_2():
# Локальная переменная для func_2.
loc_var_2 = 'loc_var_2'
# Выведет loc_var_2 in func_2.
print(loc_var_2, 'in func_2', end='\n\n')
# Вызываем вложенную функцию func_2.
func_2()
# Вызываем объемлющую функцию.
func_1()
# name loc_var_1 is not defined.
# print(loc_var_1)
# name loc_var_2 is not defined.
# print(loc_var_2)
loc_var_1 in func_1
loc_var_2 in func_2
Пример №11. Области видимости в Python (часть 2).
Наличие обособленных локальных областей видимости и приоритет в них локальных переменных над глобальными и нелокальными переменными с теми же именами позволяет избежать неоднозначности и конфликта имен. Этому содействует и невозможность использования для функций из одного и того же пространства имен локальных переменных одной функции в области видимости другой (см. пример №12).
# Инициализировали глобальную переменную.
glob_loc_var = 'glob_loc_var in global'
# Выведет glob_loc_var in global.
print(glob_loc_var, end='\n\n')
# Объявляем 1-ю функцию.
def func_1():
# Локальная имеет приоритет над глоб. переменной.
glob_loc_var = 'glob_loc_var in func_1'
# Выведет glob_loc_var in func_1.
print(glob_loc_var)
# Локальная переменная для func_1.
loc_var_1 = 'loc_var_1 in func_1'
# Выведет loc_var_1 in func_1.
print(loc_var_1, end='\n\n')
# name 'loc_var_2' is not defined.
# print(loc_var_2)
# Объявляем 2-ю функцию.
def func_2():
# Локальная имеет приоритет над глоб. переменной.
glob_loc_var = 'glob_loc_var in func_2'
# Выведет glob_loc_var in func_2.
print(glob_loc_var)
# Локальная переменная для func_2.
loc_var_2 = 'loc_var_2 in func_2'
# Выведет loc_var_2 in func_2.
print(loc_var_2)
# name 'loc_var_1' is not defined.
# print(loc_var_1)
# Вызываем функции.
func_1()
func_2()
# name loc_var_1 is not defined.
# print(loc_var_1)
# name loc_var_2 is not defined.
# print(loc_var_2)
glob_loc_var in global
glob_loc_var in func_1
loc_var_1 in func_1
glob_loc_var in func_2
loc_var_2 in func_2
Пример №12. Области видимости в Python (часть 3).
Для случаев, когда возникает необходимость использования внутри функций именно глобальных или нелокальных переменных вместо локальных с теми же именами, в Python предусмотрены инструкции global и nonlocal. Все что нужно – это просто перечислить после ключевых слов требуемые имена через запятую (см. пример №13).
# Инициализируем глобальную переменную.
glob_var = 'Исходное значение glob_var'
# Выведет: Исходное значение glob_var.
print(glob_var)
# Объявляем объемлющую функцию.
def func_1():
# Будем использовать глобальную переменную.
global glob_var
# Изменяем значение глобальной переменной.
glob_var = 'glob_var изменена в func_1'
# Создаем локальную переменную для func_1.
loc_var = 'Исходное значение loc_var'
# Выведет: Исходное значение loc_var.
print(loc_var)
# Объявляем вложенную функцию.
def func_2():
# Будем использовать нелокальную переменную.
nonlocal loc_var
# Изменяем значение нелокальной переменной.
loc_var = 'loc_var изменена в func_2'
# Вызываем вложенную функцию func_2.
func_2()
# Выведет: loc_var изменена в func_2.
print(loc_var)
# Вызываем объемлющую функцию.
func_1()
# Выведет: glob_var изменена в func_1.
print(glob_var)
Исходное значение glob_var
Исходное значение loc_var
loc_var изменена в func_2
glob_var изменена в func_1
Пример №13. Использование инструкций global и nonlocal.
Из всего выше сказанного следует простое правило поиска имен: когда внутри функции выполняется обращение к неизвестному имени, интерпретатор пытается сначала отыскать его в локальной области видимости, затем в локальной области любой объемлющей инструкции def или в выражении lambda и далее в глобальной области видимости. Если искомого имени обнаружено не будет, интерпретатор выведет сообщение об ошибке.
Строго говоря, выделяют еще и встроенную область видимости, имена из которой автоматически добавляются в пространство имен глобальной области видимости текущего модуля. Сюда входят, например, имена встроенных типов и функций. Поэтому говоря о глобальной области видимости без дополнительных оговорок, мы будем подразумевать и встроенную область видимости.
В конце пункта хотелось бы добавить, что получить сведения о локальных и глобальных переменных можно при помощи следующих встроенных функций:
- globals() – возвращает словарь со значениями переменных из текущей глобальной области видимости модуля, в котором ключами служат имена этих переменных. Стоит заметить, что функция возвращает такой словарь даже в тех случаях, когда она вызывается из локальных областей видимости функций и методов (см. пример №14).
- locals() – обновляет и возвращает словарь со значениями переменных, использующихся в текущей локальной области видимости функции или метода, в котором ключами служат имена этих переменных. Следует иметь в виду, что значения этого словаря изменять не стоит, т.к. изменённые значения все равно могут быть проигнорированы интерпретатором. Кроме того, при использовании этой функции в глобальной области видимости модуля она вернет словарь с глобальными переменными также, как и функция globals().
# Инициализируем 1-ю глобальную переменную.
glob_var_1 = 0.1
# Объявляем объемлющую функцию.
def func_1():
# Локальная переменная для func_1.
loc_var_1 = 1
# Словарь доступных локальных переменных для func_1.
print('func_1 locals:', locals())
# Словарь глобальных переменных.
print('globals:', globals())
# Объявляем вложенную функцию.
def func_2():
# Локальная переменная для func_2.
loc_var_2 = 2
# Словарь локальных переменных для func_2.
print('func_2 locals:', locals())
# Обновляем словарь локальных переменных для func_1.
print('func_1 locals:', locals())
# Вызываем вложенную функцию func_2.
func_2()
# Вызываем объемлющую функцию.
func_1()
# Словарь глобальных переменных.
print('globals:', globals())
# Выведет тоже самое.
print('globals:', locals())
# Инициализируем 2-ю глобальную переменную.
glob_var_2 = 0.2
# Обновляем словарь глобальных переменных.
print('globals:', globals())
func_1 locals: {'loc_var_1': 1}
globals: {..., 'glob_var': 0, 'func_1': <function func_1 at 0x000001C73638A200>}
func_1 locals: {'loc_var_1': 1, 'func_2': .func_1..func_2 at 0x00000204AF7A8700>}
func_2 locals: {'loc_var_2': 2}
globals: {..., 'glob_var': 0, 'func_1': <function func_1 at 0x000001C73638A200>}
globals: {..., 'glob_var': 0, 'func_1': <function func_1 at 0x000001C73638A200>}
globals: {..., 'glob_var': 0, 'func_1': <function func_1 at 0x000001C73638A200>, 'glob_var_2': 0.2}
Пример №14. Использование функций globals() и locals().
Как видно из примера, обе функции отображают в словаре только те переменные, которые известны интерпретатору на момент их вызова. Поэтому, если переменные объявляются в коде скрипта ниже вызовов этих функций, их в возвращаемых словарях не будет.
lambda-выражения или анонимные функции в Python
Поскольку в Python инструкции не могут использоваться внутри выражений, для этих целей в язык была введена дополнительная возможность в виде специальных конструкций, называемых lambda-выражениями и позволяющих создавать объекты функций там, где интерпретатор ожидает встретить выражение. Эти конструкции еще называют анонимными или безымянными функциями, т.к. в отличие от инструкций def они хоть и создают функции, но не связывают их с именами.
lambda arg_1, arg_2, ..., arg_n: <выражение>
.
Здесь стоит заметить, что после двоеточия записывается именно выражение, а не блок инструкций. По сути такое выражение сродни тому, что мы помещаем в инструкцию return при определении обычной функции. Это конечно же делает lambda-выражения менее универсальными по сравнению с инструкциями def, но такая реализация предусмотрена намеренно, т.к. lambda-выражения предназначены для создания простых функций, которые бы не усложняли выражения, в то время как инструкции def представляют собой инструменты для решения более сложных задач (см. пример №14).
# Обычная инструкция def неявно сразу
# связывает объект функции с именем.
def func(x, y, z): return x + y + z
# Выведет 30.
print(func(5, 10, 15))
# Создаем объект анонимной ф-ции и
# явно присваиваем его переменной.
lambda_1 = lambda x, y, z: x + y + z
# Выведет 30.
print(lambda_1(5, 10, 15), end='\n\n')
# Используем значения по умолчанию.
lambda_2 = lambda x=8, y=4: x/y
# Выведет 2.0.
print('8/4 =', lambda_2())
# Выведет 3.0.
print('12/4 =', lambda_2(12), end='\n\n')
# Используем сбор аргументов в кортеж.
lambda_3 = lambda *nums: sum(nums)
# Выведет 5.
print('sum((2, 3)) =', lambda_3(2, 3), end='\n\n')
# Используем простейшую логику.
lambda_3 = lambda x, y: x/y if y != 0 else None
# Выведет 4.0.
print('20/5 =', lambda_3(20, 5))
# Выведет None.
print('20/0 =', lambda_3(20, 0))
# Ошибка: инструкции в теле lambda запрещены.
# lambda_4 = lambda x, y: if y != 0: x/y
30
30
8/4 = 2.0
12/4 = 3.0
sum((2, 3)) = 5
20/5 = 4.0
20/0 = None
Пример №14. Использование lambda-выражений (часть 1).
Как видим, lambda-выражения действительно очень похожи на обыкновенные функции: в них также действуют специальные режимы сопоставления аргументов, имеется тело с кодом, а на выходе получается объект функции. Однако, повторимся, в теле lambda-выражений может находиться только одно единственное выражение, в то время как использование даже простейших инструкций вообще запрещено. Кроме того, возвращаемый объект функции нужно либо сразу вызывать при помощи круглых скобок, либо присваивать переменной явно. Зато lambda-выражения можно смело использовать внутри других выражений (см. пример №15).
# Список объектов функций.
li = [lambda x: x**2, lambda x: x**3]
# Выведет 9.
print('3*3 =', li[0](3))
# Выведет 27.
print('3*3*3 =', li[1](3), end='\n\n')
# Создание и вызов внутри выражения.
expr = 5 + (lambda x, y: x**y)(5, 2)
# Выведет 30.
print(expr)
# Вложенное lambda-выражение (но зачем усложнять?).
expr = (lambda x: 7 + (lambda a, b: a**b)(x, 2))(5)
# Выведет 32.
print(expr)
3*3 = 9
3*3*3 = 27
30
32
Пример №15. Использование lambda-выражений (часть 2).
В нашем примере вложенное lambda-выражение, как и положено вложенным функциям, имеет доступ к переменной x в объемлющем lambda-выражении. Но выглядит код явно замысловато! Поэтому в интересах удобочитаемости кода лучше такие трюки не использовать.
Рекурсивные функции в Python
Рекурсивная функция – это функция, которая содержит код вызова самой себя в целях организации циклического процесса.
Рекурсивные функции являются вполне обычными функциями со всеми присущими им особенностями. Разница лишь в том, что внутри тела такие функции содержат вызов самой себя. Звучит это как-то странно и непонятно, в особенности для начинающих программистов, поэтому стоит сразу же обратиться к практике. В качестве примера, давайте рассмотрим использование рекурсивной функции в ходе решения классической задачи нахождения факториала числа (см. пример №16).
# Обычное объявление функции.
def recursive_func(n):
# Условие завершения рекурсий.
if n == 1:
# Возвращаем 1.
return n
# Иначе
else:
# используем вызов самой себя,
# уменьшив значение аргумента на 1.
return n*recursive_func(n-1)
# Выводим 3!, т.е. 6.
print(recursive_func(3))
# Выводим 5!, т.е. 120.
print(recursive_func(5))
6
120
Пример №16. Рекурсивные функции в Python.
В нашем примере код рекурсивной функции содержит условную инструкцию if, которая останавливает рекурсии (вызовы самой себя) только в том случае, когда значение переданного ей аргумента будет равно единице. Если же при текущем вызове функции значение аргумента будет больше единицы, в блоке else условной инструкции if будет предпринята попытка вычисления выражения n*recursive_func(n-1), использующее вызов функцией самой себя. При этом прежде, чем вернуть конечный результат в точку вызова функции в программе, наша рекурсивная функция будет вызывать себя саму до тех пор, пока в выражении для возврата не останется ее незавершенных вызовов. Например, для n=4, наш факториал схематически будет формироваться следующим образом: return 4*recursive_func(4-1) -> return 4*(return 3*recursive_func(3-1)) -> return 4*(return 3*(return 2*recursive_func(2-1))) -> return 4*(return 3*(return 2*(return 1))) -> return 4*(return 3*(return 2*1)) -> return 4*(return 3*2*1) -> return 4*3*2*1.
Следует заметить, что в языке Python рекурсии используются не так часто, как в некоторых других языках программирования, поскольку в Python особое значение придается простым процедурным инструкциям вроде цикла while, который более естественно подходит для решения аналогичных задач. Тем не менее рекурсии могут быть весьма полезными для реализации обхода сложных структур с произвольной организацией данных, поэтому взять их на вооружение однозначно стоит.
Декораторы функций в Python
Декоратор (от англ. decorator) – это функция, которая используется в качестве «обертки» другой функции с целью изменения ее поведения или расширения функциональности без непосредственного изменения кода самой функции.
Декораторы могут быть весьма полезны для расширения возможностей функций из сторонних библиотек, код которых мы не можем изменять, а также для изменения поведения или расширения функциональности методов класса (о классах мы поговорим чуть позже). Все что нам нужно, это написать собственный или использовать уже готовый декоратор с требуемой дополнительной функциональностью, передав ему объект целевой функции или метода для декорирования.
В качестве примера давайте создадим собственный простейший декоратор и на его основе познакомимся с этим понятием поближе (см. пример №17).
# Объект ф-ции передается декоратору в качестве аргумента.
def decorator_func(func):
# Внутри декораторы содержат функцию-обертку, которая
# изменяет поведение декорируемой функции func.
def wrapper_func(arg):
# Добавляем подсказку.
print('Начало выполнения функции!')
# Вызываем саму декорируемую функцию.
print(func(arg))
# Добавляем еще одну подсказку.
print('Выполнение функции закончено!')
# Возвращаем объект измененной func.
return wrapper_func
# Определим обычную польз. функцию.
def my_func(arg):
# Просто возвращаем аргумент.
return arg
# Получаем объект измененной функции my_func.
my_func_1 = decorator_func(my_func)
# Вызываем измененную функцию.
my_func_1('Выполняюсь!')
print()
# Декорируем встроенную функцию sum.
my_func_2 = decorator_func(sum)
# Вызываем измененную функцию.
my_func_2([1, 2, 3])
Начало выполнения функции!
Выполняюсь!
Выполнение функции закончено!
Начало выполнения функции!
6
Выполнение функции закончено!
Пример №17. Декораторы функций (часть 1).
Итак, в нашем примере мы создали декоратор, который добавляет декорируемым функциям сообщения о старте и финише их выполнения, а также выводит возвращаемые функциями значения на экран. При этом мы не вносили никаких изменений в код декорируемых функций. Все изменения и дополнения произошли внутри тела декоратора, в котором мы определили специальную функцию-обертку, содержащую дополнительный код и вызов исходной версии функции. Далее, использовав объект функции-обертки в качестве возвращаемого значения декоратора и присвоив вызов декоратора переменной, мы получили готовый к использованию объект усовершенствованной версии переданной декоратору в качестве аргумента функции.
Таким образом, декораторы функций в общем случае принимают на вход исходную версию какой-либо функции или метода, а возвращают обертку переданной функции или метода, но уже с дополнительной функциональностью. При этом в случаях, когда исходная версия функции становится больше не нужна, возвращаемую декоратором обертку присваивают переменной с именем исходной функции, тем самым полностью заменяя ее.
Поскольку в Python декораторы используются довольно часто, для упрощения процедуры получения декорированных объектов используется специальный синтаксис, предусматривающий использование имени декоратора с префиксом в виде символа @ непосредственно перед строкой, содержащей заголовок определения декорируемой функции или метода (см. пример №18).
# Объект ф-ции передается декоратору в качестве аргумента.
def decorator_func(func):
# Внутри декораторы содержат функцию-обертку, которая
# изменяет поведение декорируемой функции func.
def wrapper_func(arg):
# Добавляем подсказку.
print('Начало выполнения функции!')
# Вызываем саму декорируемую функцию.
print(func(arg))
# Добавляем еще одну подсказку.
print('Выполнение функции закончено!')
# Возвращаем объект измененной func.
return wrapper_func
# Декорируем определяемую функцию.
@decorator_func
# Определим обычную польз. функцию.
def my_func(arg):
# Просто возвращаем аргумент.
return arg
# Вызываем измененную функцию.
my_func('Выполняюсь!')
Начало выполнения функции!
Выполняюсь!
Выполнение функции закончено!
Пример №18. Декораторы функций (часть 2).
Благодаря наличию такого синтаксиса нам не пришлось использовать в примере более длинную инструкцию my_func = decorator_func(my_func), что сделало код более коротким и читабельным.
Атрибуты функций, аннотации и документирование в Python
Поскольку функции в Python являются обычными объектами, мы можем без каких-либо сложностей получать базовый доступ к предопределенным атрибутам функций, а также добавлять, изменять или удалять собственные атрибуты (см. пример №19).
# Объявляем функцию и получаем объект.
def func(n): print(n*2)
# <function main.<locals>.func at 0x0000025137AA4670>.
print(func, end='\n\n')
# Выводим ее встроенные атрибуты.
print(dir(func))
# Выводим имя функции.
print(func.__name__)
# Выводим размер функции в байтах.
print(func.__sizeof__())
# Создаем свой атрибут в виде свойства.
func.count = 0
# Выводим значение атрибута.
print(func.count)
# Используем атрибут в вызове функции.
# Выведет 0.
func(func.count)
# Изменяем значение нашего атрибута.
func.count += 1
# Выведет 1.
print(func.count)
# Выведет 2.
func(func.count)
<function main.<locals>.func at 0x0000025137AA4670>
['__annotations__', '__builtins__', '__call__',...
func
128
0
0
1
2
Пример №19. Атрибуты функций в Python.
Как видим, для получения списка всех предопределенных атрибутов функции нужно просто воспользоваться встроенной функцией dir. Если же необходим доступ к конкретному атрибуту, следует указать его имя в формате func.attr, где func – имя функции, а attr – имя атрибута. Что касается пользовательских атрибутов, то они создаются и изменяются простым присваиванием требуемого значения имени атрибута, который также записывается после имени функции через точку в формате func.attr. Собственные атрибуты позволяют эффективно и просто хранить информацию о состоянии непосредственно в объекте функции, отказавшись от использования других приемов, таких как применение глобальных или нелокальных переменных и классов. Огромным плюсом при этом является то, что в отличие от нелокальных переменных, атрибуты функции доступны в любом месте программы, где доступна сама функция. Так что в некотором смысле их можно рассматривать, как имитацию статических локальных переменных, имеющихся в некоторых других языках программирования, т.к. они тоже сохраняют свои значения между вызовами функции.
Начиная с версии Python 3.0 у объектов функций появился новый встроенный атрибут __annotations__, который предназначен для хранения краткого описания (аннотаций) аргументов функции и ее возвращаемого значения. Аннотации совершенно необязательны, но если они присутствуют, то их всегда можно получить через этот атрибут, воспользовавшись синтаксической конструкцией func.__annotations__ (см. пример №20).
# Объявляем аннотированную функцию.
def func(name: 'имя', age: int) -> str:
'''Строка документирования: func.__doc__'''
# Составляем ник пользователя.
nick = name + '_' + str(age)
# Возвращаем его.
return nick
# Выведет Петр_35.
print(func('Петр', 35))
# Строка документирования: func.__doc__.
print(func.__doc__)
# {'name': 'имя', 'age': <class 'int'>, 'return': <class 'str'>}.
print(func.__annotations__)
Петр_35
Строка документирования: func.__doc__
{'name': 'имя', 'age': <class 'int'>, 'return': <class 'str'>}
Пример №20. Аннотации и документирование функций в Python.
Как видно из примера, синтаксически аннотации функции находятся в заголовке инструкции def в виде произвольных выражений, которые ассоциируются с ее аргументами и возвращаемым значением. Для аргументов аннотации указываются через двоеточие сразу после имени аргумента, а для возвращаемого значения – после пары символов -> вслед за списком аргументов. Все аннотации, если конечно они присутствуют в объявлении функции, интерпретатор собирает в словарь, который затем присоединяет к объекту созданной функции. Имена аргументов в этом словаре становятся ключами, а сами аннотации их значениями. При этом для возвращаемого значения аннотация сохраняется под ключом return.
Cтоит обратить внимание на еще один важный момент: в аннотированных аргументах, которые используют значения по умолчанию, аннотация записывается перед значением по умолчанию в формате def func(arg_1: annot_1 = val_1, arg_2: annot_2 = val_2, ...) -> return_annot:.
Что касается строки документирования, то она должна записываться в начале тела функции в тройных кавычках. Интерпретатор автоматически присоединит документацию к объекту функции и она станет доступна в качестве значения его предопределенного атрибута __doc__.
Концепции проектирования функций в Python
Теперь, когда мы получили достаточно ясное представление о функциях в Python, хотелось бы подытожить тему несколькими полезными рекомендациями.
- Каждая хорошо спроектированная функция должна иметь одно единственное назначение и решать только одну простую задачу. Например, если функция позволяет одновременно перемножать и складывать числа, то наверняка стоит подумать над тем, чтобы разбить ее на две отдельные и более простые функции.
- Каждая функция должна иметь относительно небольшой размер. Если ваша функция начинает занимать несколько экранов – это явный признак того, что было бы неплохо разбить ее на несколько более простых частей. Функции с большой глубиной вложенности часто свидетельствует о промахах в проектировании, тем более в языке Python, которому присущи краткость и лаконичность.
- Используйте для передачи значений функции аргументы, а для возврата результатов – инструкцию return. Функция должна быть максимально независимой от того, что происходит за ее пределами.
- Старайтесь использовать глобальные переменные внутри тела функции только в том случае, когда это действительно необходимо. Это поможет избежать зависимости от внешних факторов и проблем с согласованностью компонентов программ, что, как следствие, существенно облегчит их отладку в дальнейшем.
- Следите за тем, чтобы ваши функции не воздействовали на изменяемые аргументы, если вызывающая программа не предполагает этого. Ведь получая в качестве аргументов, например, списки или словари, функции могут оказывать воздействие на части этих изменяемых типов данных. В результате такие функции, опять же, становятся слишком тесно связаны и зависимы от внешней среды, что может сделать их уж слишком специфичными и неустойчивыми.
- Избегайте непосредственного изменения переменных вашими функциями в других модулях программы. Используйте для этих целей отдельные функции доступа, сведя прямую зависимость к минимуму, как и в случае с глобальными переменными.
Краткие итоги параграфа
- Функция в программировании – это блок программного кода на этом языке, который определяется один раз и далее может быть использован многократно.
- Функции позволяют существенно уменьшить время и трудозатраты на разработку приложений, а также их дальнейшее сопровождение за счет следующих преимуществ: дают возможность разбить сложную систему на небольшие и легко управляемые части, каждая из которых может разрабатываться отдельно, обеспечивают многократное использование программного кода, уменьшают его избыточность, повышают читабельность исходного кода, упрощают его редактирование.
-
Для создания функций в Python предназначена инструкция def, которая создает объект функции и связывает его с именем.
В общем виде инструкция имеет следующий формат:
Примером определения функции может служить инструкция def f(a, b): return a + b.def <Имя функции>(arg_1, arg_1,... arg_n): <Тело функции> return <Возвращаемое значение>
- Инструкция return не является обязательной. При ее отсутствии функция будет завершать свою работу по достижении потоком управления конца тела функции. При этом технически она все равно будет неявно возвращать результат в виде объекта None, однако он обычно просто игнорируется.
- Осуществляются вызовы функций только после их определения при помощи имени функции и круглых скобок с передаваемыми функции значениями аргументов, которые, если их несколько, должны перечисляться через запятую. Например, для определенной выше функции вызов может иметь вид x = f(43, 85).
-
В Python имеется целый ряд специальных режимов сопоставления аргументов, при использовании которых следует придерживаться следующих правил:
- в заголовке определяемой функции аргументы должны указываться в следующем порядке: позиционные аргументы (arg_1, arg_2, ...), позиционные аргументы со значениями по умолчанию (arg_1=val_1, arg_2=val_2, ...), аргумент со звездочкой для сбора дополнительных позиционных аргументов в кортеж (*args), именованные аргументы (named_arg_1, named_arg_2, ...), именованные аргументы со значениями по умолчанию (named_arg_1=val_1, named_arg_2=val_2, ...) и аргумент с двумя звездочками для сбора дополнительных именованных аргументов в словарь (**named_args);
- при вызове функции порядок передачи аргументов должен быть следующим: обычные позиционные аргументы (arg_1, arg_2, ...), позиционные аргументы в форме *args (будут распакованы интерпретатором в значения arg_1, arg_2, ...), обычные именованные аргументы (named_arg_1=val_1, named_arg_2=val_2, ...) и в самом конце именованные аргументы в форме **named_args (будут распакованы интерпретатором в пары named_arg_1=val_1, named_arg_2=val_2, ...).
-
В пределах модуля имена могут находиться в трех основных областях видимости, которые определяются местом инициализации переменных.
- Если присваивание переменной выполняется внутри инструкции def, переменная становится локальной для этой функции. Это касается как аргументов функции, так и переменных, созданных в теле функции. Локальные переменные доступны только внутри своей функции, т.е. в текущей локальной области видимости, и недоступны за ее пределами, включая другие локальные области видимости невложенных в нее функций.
- Если присваивание производится в пределах объемлющей инструкции def, переменная становится локальной для данной объемлющей функции и нелокальной для данной вложенной функции. В общем случае такая переменная будет доступна внутри любой вложенной функции при отсутствии в ней локальной переменной с таким же именем.
- Если присваивание производится на верхнем уровне модуля (файла скрипта) за пределами всех инструкций def, переменная становится глобальной для всего файла. В общем случае глобальная переменная будет доступна внутри любой функции текущего модуля при отсутствии в ней локальной переменной с таким же именем.
- Получить информацию о глобальных и локальных переменных известных интерпретатору на данный момент можно при помощи встроенных функций globals() и locals(), которые возвращают словари со значениями соответствующих переменных, имена которых используются в словарях в качестве ключей.
- Поиск имен интерпретатором осуществляется следующим образом: когда внутри функции выполняется обращение к неизвестному имени, интерпретатор пытается сначала отыскать его в локальной области видимости, затем в локальной области любой объемлющей инструкции def или в выражении lambda и далее в глобальной области видимости. Как только первое подходящее имя будет найдено, поиск завершается. Если искомого имени обнаружено не будет, интерпретатор выведет сообщение об ошибке.
- Для создания простых функций непосредственно внутри выражений в Python используются lambda-выражения, которые еще называют анонимными или безымянными функциями, т.к. они хоть и создают функции, но не связывают их с именами. При этом важно помнить, что в теле анонимной функции может содержаться только одно выражение и никаких инструкций.
- В Python допускается использование рекурсивных функций, которые позволяют осуществлять вызов самих себя до выполнения особого условия, останавливающего дальнейшие рекурсии. Используются рекурсивные функции не так часто, как в некоторых других языках программирования, зато они могут оказаться весьма полезными для реализации обхода структур с произвольной организацией данных.
- Также для Python характерно использование декораторов, т.е. специальных функций-оберток, которые позволяют изменять поведение и расширять функциональность передаваемых им в качестве аргументов других функций и методов, что бывает весьма полезным для расширения возможностей функций из сторонних библиотек, код которых мы не можем изменять, а также для изменения поведения или расширения функциональности методов классов.
- Поскольку функции в Python являются обычными объектами, у них имеются предопределенные атрибуты, а также могут добавляться, изменяться или удаляться пользовательские атрибуты. В общем случае доступ к атрибутам можно получить при помощи имени атрибута, указанного через точку после имени функции.
- Начиная с версии Python 3.0 у объектов функций появился новый встроенный атрибут __annotations__, используемый для хранения аннотаций к аргументам функции и возвращаемому значению. В общем виде синтаксис записи аннотаций выглядит так: def func(arg_1: annot_1, ... arg_k: annot_k = val_k, ...) -> return_annot:.
- Помимо аннотаций в функции разрешается добавлять строки документирования. Такая строка должна записываться в начале тела функции в тройных кавычках. Интерпретатор автоматически присоединит ее к объекту функции и далее созданная документация станет доступна в качестве значения его предопределенного атрибута __doc__.
- В ходе проектирования функций следует придерживаться следующих основных правил: функция должна быть максимально короткой и выполнять одну простую задачу; значения в функцию должны передаваться через аргументы, а результат возвращаться через инструкцию return; глобальные переменные должны использоваться внутри функции только в том случае, когда это действительно необходимо; функции не должны воздействовать на изменяемые аргументы, если вызывающая программа не предполагает этого; изменение переменных в других модулях следует осуществлять через специально созданные для этого функции доступа, избегая непосредственного их изменения.
Вопросы и задания для самоконтроля
1. Дайте определение функции в программировании. Перечислите основные преимущества использования функций. Показать решение.
Ответ. Функция в программировании – это блок программного кода на этом языке, который определяется один раз и далее может быть использован многократно.
Функции позволяют существенно уменьшить время и трудозатраты на разработку приложений, а также их дальнейшее сопровождение за счет следующих преимуществ:
- дают возможность разбить сложную систему на небольшие и легко управляемые части, каждая из которых может разрабатываться отдельно;
- обеспечивают многократное использование программного кода;
- уменьшают его избыточность;
- повышают читабельность исходного кода;
- упрощают его редактирование.
2. В какой момент времени интерпретатор Python создает функции? Разрешается ли вызывать функции до момента их определения? Показать решение.
Ответ. Функция создается, когда поток выполнения программы достигает инструкции def и выполняет ее. Эта инструкция создает объект функции и связывает его с именем функции. С этого момента функция становится доступной для вызовов: для этого нужно к имени функции добавить круглые скобки со значениями аргументов внутри. Вызывать функцию до момента ее определения в Python запрещается. Стоит заметить, что в некоторых других языках программирования такое возможно за счет того, что определения функций поднимаются вверх скриптов. Но в Python вызывать функцию до момента ее определения запрещается!
3. Когда выполняется непосредственно сам программный код, вложенный в тело функции? Показать решение.
Ответ. Тело функции выполняется каждый раз при вызове уже определенной функции.
4. Что возвращает функция, в которой отсутствует инструкция return? Показать решение.
Ответ. Инструкция return не является обязательной. При ее отсутствии функция будет завершать свою работу по достижении потоком управления конца тела функции. При этом технически она все равно будет неявно возвращать результат в виде объекта None, однако он обычно просто игнорируется.
5. Какой из представленных вариантов определения функции не содержит ошибок: dfn f(x, y): return x*y, def f(x, y): return x*y или def f{x, y}: return x*y. Показать решение.
Ответ. def f(x, y): return x*y
6. Каковы будут результаты вызовов функций в коде условия? Объясните свои ответы, а затем запустите скрипт и проверьте себя. Показать решение.
# 1.
def func_1(a, b=2, c=3): print(a, b, c)
func_1(1, 2)
# 2.
def func_2(a, b, c=3): print(a, b, c)
func_2(1, c=4, b=5)
# 3.
def func_3(a, *p_args): print(a, p_args)
func_3(1, 2, 3)
# 4.
def func_4(a, b, c=3): print(a, b, c)
func_4(2, *(4, 5))
# 5.
def func_5(a, **n_args): print(a, n_args)
func_5(1, c=3, b=2)
# 6.
def func_6(a, *, b, **n_args): print(a, b, n_args)
func_6(1, c=3, b=2)
# Два аргумента передали по умолчанию.
def func_1(a, b=2, c=3): print(a, b, c)
# Выведет 1 2 3, т.к. позиционные
# аргументы сопоставляются по позициям.
func_1(1, 2)
# По умолчанию передали только 1 арг-т.
def func_2(a, b, c=3): print(a, b, c)
# Выведет 1 5 4, т.к. передаем арг-ты
# по именам хоть и не в том порядке.
func_2(1, c=4, b=5)
# Доп. поз-ные арг-ты собираем в кортеж.
def func_3(a, *p_args): print(a, p_args)
# Выведет 1 (2, 3), т.к. два дополнительных
# аргумента добавились в кортеж.
func_3(1, 2, 3)
# Имеем 1 позиционный арг-т по умолчанию.
def func_4(a, b, c=3): print(a, b, c)
# Выведет 2 4 5, т.к. кортеж распаковался
# в позиционные значения 4 и 5.
func_4(2, *(4, 5))
# Доп. именованные арг-ты собираем в словарь.
def func_5(a, **n_args): print(a, n_args)
# Выведет 1 {'c': 3, 'b': 2}, т.к. было передано
# 2 именованных аргумента хоть и не в том порядке.
func_5(1, c=3, b=2)
# После * определяем именованные аргументы.
def func_6(a, *, b, **n_args): print(a, b, n_args)
# 1 2 {'c': 3}, т.к. а - поз-ный арг-т, b - имен-ный,
# с - доп-ный именованный (он и добавился в словарь).
func_6(1, c=3, b=2)
1 2 3
1 5 4
1 (2, 3)
2 4 5
1 {'c': 3, 'b': 2}
1 2 {'c': 3}
7. Какие переменные в Python называются глобальными, а какие локальными? Доступны ли глобальные переменные внутри функций, а локальные на верхнем уровне модуля? Показать решение.
Ответ. Переменные объявленные за пределами всех инструкций def на верхнем уровне модуля (файла скрипта), называются глобальными. В общем случае глобальная переменная будет доступна внутри любой функции текущего модуля при отсутствии в ней локальной переменной с таким же именем. Переменные объявленные внутри функции называются ее локальными переменными. Локальные переменные доступны только внутри своей функции, т.е. в текущей локальной области видимости, и недоступны за ее пределами, включая другие локальные области видимости невложенных в нее функций.
8. Что будет выведено на экран фрагментами кода условия? Объясните свои ответы, а затем запустите скрипт и проверьте себя. Показать решение.
# 1.
x_1 = 1
def func_1(): print(x_1)
func_1()
# 2.
x_2 = 2
def func_2(): x_2 = 22
func_2()
print(x_2)
# 3.
x_3 = 3
def func_3():
x_3 = 33
print(x_3)
func_3()
print(x_3)
# 4.
x_4 = 4
def func_4():
global x_4
x_4 = 44
func_4()
print(x_4)
# 5.
x_5 = 5
def func_5():
x_5 = 55
def nested_5(): print(x_5)
nested_5()
func_5()
print(x_5)
# 6.
x_6 = 6
def func_6():
x_6 = 66
print(x_6)
def nested_6():
nonlocal x_6
x_6 = 666
nested_6()
print(x_6)
func_6()
print(x_6)
# Глобальная переменная.
x_1 = 1
# Глоб. пер-е доступны везде.
def func_1(): print(x_1)
# Выведет 1.
func_1()
# Глобальная переменная.
x_2 = 2
# Лок-ная имеет приоритет внутри ф-ции.
def func_2(): x_2 = 22
# Ничего не выводит.
func_2()
# Выведет значение глоб. пер-ной, т.е. 2.
print(x_2)
# Глобальная переменная.
x_3 = 3
def func_3():
# Локальная переменная.
x_3 = 33
# Лок-ная имеет приоритет внутри ф-ции.
print(x_3)
# Выведет значение лок. пер-ной, т.е. 33.
func_3()
# Вне ф-ции лок. пер-ная недоступна.
# Выведет значение глоб. пер-ной, т.е. 3.
print(x_3)
# Глобальная переменная.
x_4 = 4
# Выведет новое значение глоб. пер-ной, т.е. 44.
print(x_4)
def func_4():
# Использ. глоб. пер-ную.
global x_4
# Значение присваивается глоб. пер-ной.
x_4 = 44
# Вызов изменяет значение глоб. пер-ной.
func_4()
# В глоб. области лок-ные пер-ные недоступны.
# Выведет новое значение глоб. пер-ной, т.е. 44.
print(x_4)
# Глобальная переменная.
x_5 = 5
def func_5():
# Локальная переменная для func_5.
x_5 = 55
# Локальные переменные объемлющей ф-ции
# доступны во всех вложенных функциях.
def nested_5(): print(x_5)
# Выведет 55 при вызове func_5().
nested_5()
# Выведет 55.
func_5()
# В глоб. области лок-ные пер-ные недоступны.
# Выведет значение глоб. пер-ной, т.е. 5.
print(x_5)
# Глобальная переменная.
x_6 = 6
def func_6():
# Локальная переменная для func_6.
# Нелокальная переменная для nested_6.
x_6 = 66
# Выведет 66 при вызове func_6().
print(x_6)
def nested_6():
# Использ. нелок-ную. пер-ную.
nonlocal x_6
# Присваиваем значение нелок-ной пер-ной.
x_6 = 666
# Вызов изменяет нелок-ную пер-ную.
nested_6()
# Выведет 666 при вызове func_6().
print(x_6)
# Выведет 666.
func_6()
# В глоб. области лок-ные пер-ные недоступны.
# Выведет значение глоб. пер-ной, т.е. 6.
print(x_6)
1
2
33
3
4
44
55
5
66
666
6
9. Сосчитайте локальные переменные функции func(x). Сколько локальных переменных будет отображено в словаре loc_vars при первом и втором вызовах функции locals() и почему? Показать решение.
def func(x):
y = 5
z = 10
loc_vars = locals()
sum = x + y + z
loc_vars = locals()
return sum
print(func(20))
Ответ. Всего в локальной области видимости функции func(x) имеется пять локальных переменных. Все они будут перечислены в словаре, который возвращает второй вызов функции locals(). Однако после первого вызова функции в переменной loc_vars будет содержаться словарь лишь с тремя локальными переменными x, y и z, т.к. только они будут известны интерпретатору на момент вызова функции. Сама локальная переменная loc_vars становится известной интерпретатору уже после вызова функции locals() в момент присваивания ей значения (инструкция присваивания имеет правую ассоциативность).
# Объявляем функцию.
def func(x):
# Объявляем локальные переменные.
y = 5
z = 10
# Получаем словарь доступных локальных переменных.
loc_vars = locals()
print('loc_vars:', loc_vars)
# Объявляем еще одну локальную переменную.
sum = x + y + z
# Обновляем словарь локальных переменных для func_1.
loc_vars = locals()
print('loc_vars:', loc_vars)
return sum
# Вызываем функцию.
print(func(20))
loc_vars: {'x': 20, 'y': 5, 'z': 10}
loc_vars: {'x': 20, 'y': 5, 'z': 10, 'loc_vars': {...}, 'sum': 35}
35
10. Перечислите основные отличия lambda-выражения от инструкции def. Показать решение.
Ответ. В отличие от обычной инструкции объявления функции def лямбда-выражение обладает следующими особенностями: создает объект функции, но не связывает его с именем; является выражением и может использоваться внутри выражений; внутри тела может содержать только одно выражение и никаких инструкций.
11. Что представляют из себя рекурсивные функции? Показать решение.
Ответ. Рекурсивные функции – это функции, которые содержат код вызова самой себя в целях организации циклического процесса. Во всем остальном они представляют собой вполне обычные функции со всеми присущими им особенностями.
12. Каков синтаксис использования одной функции в качестве декоратора другой? Показать решение.
Ответ. Необходимо написать в строке, предшествующей строке с заголовком определяемой декорируемой функции, имя декоратора с префиксом в виде символа @.
# Это наш декоратор.
def decorator_func(func):
# Внутри декораторы содержат функцию-обертку, которая
# изменяет поведение декорируемой функции func.
def wrapper_func(arg):
# Добавляем подсказку.
print('Начало выполнения функции!')
# Вызываем саму декорируемую функцию.
print(func(arg))
# Добавляем еще одну подсказку.
print('Выполнение функции закончено!')
# Возвращаем объект измененной func.
return wrapper_func
# Декорируем определяемую функцию.
@decorator_func
# Определим обычную польз. функцию.
def my_func(arg):
# Просто возвращаем аргумент.
return arg
# Вызываем измененную функцию.
my_func('Выполняюсь!')
Начало выполнения функции!
Выполняюсь!
Выполнение функции закончено!
13. Каких наиболее общих рекомендаций следует придерживаться в ходе проектирования функций? Показать решение.
Ответ. В ходе проектирования функций следует придерживаться следующих основных правил: функция должна быть максимально короткой и выполнять одну простую задачу; значения в функцию должны передаваться через аргументы, а результат возвращаться через инструкцию return; глобальные переменные должны использоваться внутри функции только в том случае, когда это действительно необходимо; функции не должны воздействовать на изменяемые аргументы, если вызывающая программа не предполагает этого; изменение переменных в других модулях следует осуществлять через специально созданные для этого функции доступа, избегая непосредственного их изменения.
14. Дополнительные тесты по теме расположены в разделе «Функции» нашего сборника тестов по основам Пайтона.
15. Дополнительные упражнения и задачи по теме расположены в разделе «Функции» нашего сборника задач и упражнений.