Функции
Contents
Функции#
Объявление функций#
Про определение функций в python
можно подробно прочитать в документации по ссылке.
Объявление функции (function definition
) состоит из заголовка и тела.
Заголовок начинается с ключевого слова
def
, за которым должно следовать название (имя) функции и список формальных параметров. Завершается заголовок символом двоеточия.Далее приводится тело функции, инструкции в котором приводятся с постоянным отступом вправо относительно заголовка функции.
Первой строкой в теле функции допускается указывать документирующую строку.
def имя_функции(параметр1, ..., параметрN):
"Документирующая строка."
Первая инструкция в теле функции
...
Последняя инструкция в теле функции
Инструкция снаружи тела функции
Когда интерпретатор встречает определение функции, он создаёт вызываемый объект-функцию и связывает этот объект с указанным при объявлении именем. Это имя также функции сохраняется в атрибут __name__
объекта-функции.
def add(x, y):
"""Return the sum of its arguments"""
return x + y
print(type(add))
print(add.__name__)
<class 'function'>
add
В ячейке выше на первой строке мы объявили функцию с именем add
принимающую два аргумента под именами x
и y
.
def add(x, y):
На следующей строке располагается документирующая строка.
def add(x, y):
"""Return the sum of its arguments"""
Документирующая строка сохраняется в атрибут __doc__
объекта функции и также выводится в справке по функции, которую можно сгенерировать встроенной функцией help.
print(add.__doc__)
print("-" * 40)
help(add)
Return the sum of its arguments
----------------------------------------
Help on function add in module __main__:
add(x, y)
Return the sum of its arguments
На последней строке функция возвращает результат вычисления выражения x + y
вызывающему коду.
def add(x, y):
"""Return the sum of its arguments"""
return x + y
Управление потоком управления и возвращение значений из функции#
При вызове функции поток управления программой передаётся этой функции. Поток управления программой возвращается обратно вызывающему коду, когда исполнение функций завершается, что может произойти по трём причинам:
встречено ключевое слово
return
;достигнут конец функции;
возникло необработанное исключение, которое начало распространяться по стеку вызовов.
Последний случай разбирается позднее в разделе “Исключения”, разберем первые два.
Если встречается конструкция вида
def имя_функции():
...
return expression
т.е. ключевое слово return
и некоторое выражение expression
, то вызывающему коду возвращается результат вычисления expression
. Именно такая ситуация и встречается в определенной выше функции add
: в качестве expression
выступает выражение x + y
.
Если же после ключевого слова return
ничего не стоит, т.е. встречается конструкция вида
def имя_функции():
...
return
то вызывающему коду возвращается значение None
.
Если же функция заканчивается без ключевого слова return
, то после исполнения последней инструкции тела функции вызывающему коду возвращается значение None
. Т.е. следующие три определения функции эквиваленты.
def имя_функции():
print("Hello, world!")
def имя_функции():
print("Hello, world!")
return None
def имя_функции():
print("Hello, world!")
return
Таким образом функция всегда возвращает какое-то значение, если её исполнение завершилось без возникновения исключений. Это значение явно задаётся выражением справа от ключевого слова return
или возвращается None
, если ключевое слово return
не встретилось при исполнении тела функции или справа от оператора return
ничего не указано.
Функции как объекты первого класса#
Прочитав объявление функции, интерпретатор создаёт вызываемый объект-функцию типа function
и связывает его с указанным при объявлении функции именем. При этом объекты функционального типа function
ничем не выделяются на фоне объектов всех остальных типов. Как и с любыми другими объектами в python
можно совершать следующие операции и над функциями:
присвоить переменной;
передать в качестве аргумента функции;
возвращать из функции;
В программировании такие объекты с такими свойствами принято называть объектами первого класса и далеко не во всех языках программирования функции являются таковыми. Но в python
это так и давайте продемонстрируем это.
Присваивание переменной#
Присвоение переменной в python
— связывание с именем. В качестве демонстрации этой возможности свяжем встроенную функцию print с именем p
и вызовем её от этого имени.
p = print
p("Теперь я могу вызывать функцию print от имени p")
Теперь я могу вызывать функцию print от имени p
Передача в качестве аргумента функции#
Передачу функции в качестве аргумента другой функции мы уже встречали на примере получения справки функцией help
. Разберем пример того, как пользовательская функция может принимать в качестве параметра объект-функцию.
def print_function_name(f):
print(f.__name__)
print_function_name(add)
print_function_name(print_function_name)
add
print_function_name
В ячейке выше определена примитивная функция print_function_name
, которая просто печатает содержимое атрибута __name__
функции. Далее на вход этой функции сначала передаётся определенная ранее функция add
. А потом на вход этой функции передаётся она сама! Да, python
настолько гибкий, что функция может обработать саму себя. Правда сложно представить себе ситуацию, где такая возможность пригодится и будет лучшей из всех возможных альтернатив.
Напишем чуть более содержательную функцию apply
, которая принимает на вход функцию f
и список l
и подменяет каждый элемент l[i]
значением f(l[i])
, т.к. применяет функцию f
к элементам списка l
на месте.
from math import sin, pi
def apply(f, l):
for i in range(len(l)):
l[i] = f(l[i])
return
x = [0, pi/6., pi/4., pi/3., pi/2.]
apply(sin, x)
print(x)
[0.0, 0.49999999999999994, 0.7071067811865476, 0.8660254037844386, 1.0]
На самом деле есть встроенная функция map, которая делает то же самое, только не на месте.
y = list(map(sin, x))
print(y)
[0.0, 0.47942553860420295, 0.6496369390800625, 0.7617599814162892, 0.8414709848078965]
Возвращение из функции. Замыкание#
Функция может возвращать функцию из себя. Распространенное применение такого приема — декораторы (см. “Декораторы”). Одна из основных причин такой распространенности — возможность реализовать шаблон замыкание (closure
).
Рассмотрим самый простой пример. Реализуем функцию, которая генерирует функцию, запоминающую момент своего создания.
from datetime import datetime
from time import sleep
def make_remembering_function():
time_of_creation = datetime.now()
def remembering_function():
print(f"I was created at {time_of_creation}")
return remembering_function
f1 = make_remembering_function()
sleep(1)
f2 = make_remembering_function()
for f in (f1, f2):
f()
I was created at 2022-10-04 15:15:34.360450
I was created at 2022-10-04 15:15:35.372219
Разберем, как этот пример работает. Обратим внимание на функцию make_remembering_function
.
def make_remembering_function():
...
def remembering_function():
...
return remembering_function
Для начала обратим внимание на то, что внутри тела этой функции объявляется другая функция remembering_function
и она же возвращается в качестве результата. При каждом вызове функции make_remembering_function
интерпретатор заново встречает объявление функции remembering_function
, создаёт функциональный объект и связывает его с именем remembering_function
в пространстве локальных имен функции make_remembering_function
(каждому вызову одной и той же функции соответствует своё уникальное пространство локальных имен).
Теперь обратим внимание, что созданная функция remembering_function
обращается к переменной time_of_creation
, которая в этой функции не объявляется.
...
time_of_creation = datetime.now()
def remembering_function():
print(f"I was created at {time_of_creation}")
...
Такие переменные считаются свободными переменными и к списку таковых даже можно получить доступ напрямую.
f1.__code__.co_freevars
('time_of_creation',)
Связывание свободных переменных с объектов происходит во время вызова функции. Поиск происходит в приоритете
nonlocals
— пространство локальных имен замыкающей функции (в данному примере пространство локальных имен функцииmake_remembering_function
);globals
— глобальное пространство имен модуля, в котором объявлена эта функция;builtins
— пространство встроенных имен.
Искусственно произвести процесс разрешения можно средствами модуля inspect, который предназначен для инспекции python
объектов. Метод inspect.getclosurevars разрешает все имена, которые используются в функции, но не объявлены в самой функции, с объектами.
import inspect
print(inspect.getclosurevars(f1))
print(inspect.getclosurevars(f2))
ClosureVars(nonlocals={'time_of_creation': datetime.datetime(2022, 10, 4, 15, 15, 34, 360450)}, globals={}, builtins={'print': <built-in function print>}, unbound=set())
ClosureVars(nonlocals={'time_of_creation': datetime.datetime(2022, 10, 4, 15, 15, 35, 372219)}, globals={}, builtins={'print': <built-in function print>}, unbound=set())
В данном примере имя time_of_creation
обнаружено в пространстве имен замыкающей функции, а print
в пространстве имен встроенных функций.
Рассмотрим несколько другой пример.
def make_greeter_function(name):
def greet():
print(f"Hi, {name}!")
return greet
ivan = make_greeter_function("Ivan")
alex = make_greeter_function("Alex")
for f in (ivan, alex):
f()
Hi, Ivan!
Hi, Alex!
Этот пример работает, основываясь на тех же механизмах, что и предыдущей, так как параметры функции — локальные переменные функции. В данном примере name
— локальная переменная функции make_greeter_function
, и она же является переменной из локального пространства имен замыкающей функции greet
(иными словами она nonlocal
для функции greet
).
Опциональные и обязательные параметры функции#
Как и в C/C++
, у параметров функции могут быть значения по умолчанию (default value
). Такие параметры ещё называют опциональными (optional parameter
), т.к. при вызове функции их можно не указывать, а параметры без значений по умолчаний называют обязательными (required parameter
).
def say_hi_n_times(name, n=1):
for _ in range(n):
print(f"Привет, {name}!")
В примере выше объявлена функция say_hi_n_times
с обязательным параметром name
и опциональным параметром n
, у которого значение по умолчанию равняется 1.
Вызвать это функцию можно двумя способами:
указав оба аргумента:
say_hi_n_times("Иван", 3)
Привет, Иван!
Привет, Иван!
Привет, Иван!
указав только обязательный параметр
name
и опустив опциональныйn
:
say_hi_n_times("Иван")
Привет, Иван!
При объявлении функции можно указывать произвольное количество как и обязательных, так и опциональных параметров, но необходимо соблюдать следующее требование: все обязательные параметры должны быть перечислены ранее всех опциональных. Т.е. допустим только следующий синтаксис.
def f(req1, ..., reqN, opt1=v1, ..., optM=vM):
...
Нарушение этого требования — синтаксическая ошибка.
def f(x=0, y):
pass
Input In [42]
def f(x=0, y):
^
SyntaxError: non-default argument follows default argument
Про механизм вычисления значения по умолчанию надо помнить две детали.
Во-первых, значение по умолчанию вычисляется при объявлении функции.
x = "before"
def f(arg=x):
print(arg)
x = "after"
f()
before
Здесь f()
печатает строку “before”, а не “after”, потому что на момент, когда интерпретатор читал объявление функции f
и создавал соответствующий объект, имя x
ссылалось на объект 5
.
Во-вторых, это значение вычисляется всего один раз. Это играет роль, если значение по умолчанию — изменяемый объект.
def f(a, L=[]):
L.append(a)
return L
print(f(1))
print(f(2))
print(f(3))
[1]
[1, 2]
[1, 2, 3]
А здесь один и тот же список разделяется между тремя вызовами функции f
. Этот список создан при объявлении функции и затем используется при всех вызовах функции без второго аргумента.
Передача аргументов в функцию по имени#
Во всех предыдущих примерах аргументы передавались в функцию позиционно, т.е. то, в какой параметр попадет значение при передаче его в качестве аргумента, определялось тем, в какой позиции он был указан при вызове функции. В python
можно передавать аргументы так же и по именам параметров, нарушая при этом порядок, в котором параметры перечислены в заголовке вызываемой функции.
Для этого при вызове функции указывается имя параметра, затем знак “=
”, а затем значение, т.е. если мы хотим передать в функцию f
значение value
в качестве параметра с именем x
, то используется следующий синтаксис.
f(...,x=value ,...)
Это сильно увеличивает количество способов передать одни и те же аргументы в функцию. Например, в случае функции с двумя параметрами существует 4 равнозначных способа её вызвать, в том числе:
передав оба аргумента позиционно;
передав первый аргумент позиционно, а второй аргумент по имени;
передав оба аргумента по имени в том же порядке, в котором они перечисленны в определении функции;
передав оба аргумента по имени в противоположном порядке, чем порядок при котором они перечислены в определении функции.
def f(param1, param2):
print(f"First parameter: {param1}. Second parameter: {param2}")
f(0, 1) # 1
f(0, param2=1) # 2
f(param1=0, param2=1) # 3
f(param2=1, param1=0) # 4
First parameter: 0. Second parameter: 1
First parameter: 0. Second parameter: 1
First parameter: 0. Second parameter: 1
First parameter: 0. Second parameter: 1
Как и в случае с опциональными параметрами в определении функции, есть ограничение на передачу параметров по имени: необходимо сначала указать все параметры, передаваемые позиционно, а только потом все параметры, передаваемые по именам.
f(param1, ..., paramN, param(N+1)=value1, ..., param(N+M)=value(N+M))
Нарушение этого правила — синтаксическая ошибка.
f(param1=0, 1)
File "C:\Users\qujim\AppData\Local\Temp/ipykernel_11924/1298529622.py", line 1
f(param1=0, 1)
^
SyntaxError: positional argument follows keyword argument
Сугубо позиционные параметры и сугубо именованные параметры#
Иногда может быть удобно сделать прием каких-то параметров только по позиции, а ряда других параметров только по имени. Для этого внутри списка параметров ставятся символы “/
” и “*
”:
все параметры, перечисленные до символа “
/
” считаются сугубо позиционными, т.е. передавать их можно только по позиции;все параметры, перечисленные после символа “
*
” считаются сугубо именованными, т.е. передавать их можно только по имени;
Ниже приведена иллюстрация.
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
----------- ---------- ----------
| | |
| По позиции или по имени |
| - Только по имени
-- Только по позиции
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
print(f"Только по позиции: {pos1} и {pos2}")
print(f"По позиции или по имени: {pos_or_kwd}")
print(f"Только по имени: {kwd1} и {kwd2}")
f(0, 1, 2, kwd1=3, kwd2=4)
print("-"*30)
f(0, 1, pos_or_kwd=2, kwd1=3, kwd2=4)
Только по позиции: 0 и 1
По позиции или по имени: 2
Только по имени: 3 и 4
------------------------------
Только по позиции: 0 и 1
По позиции или по имени: 2
Только по имени: 3 и 4
Примеры некорректных вызовов функции f
.
f(x=0, y=1, pos_or_kwd=2, kwd1=3, kwd2=4)
Первый два параметра должны быть переданы по позиции.
f(0, 1, 2, 3, 4)
Передано 5 позиционных аргумента, а в объявлении всего 3 параметра допускающих передачу по позиции.
Перехват произвольного количества позиционных параметров.#
Python
допускает синтаксис, которые позволяет перехватывать произвольное количество позиционно переданных аргументов. Для этого при объявлении функции указывается параметр с символом “*
” перед ним. Обычно такому параметру дают имя *args
(сокращение от arguments
). Ниже приведен пример такого объявления.
def f(*args):
print(f"{len(args)=}, {args=}")
Вызывать такую функцию можно передавая произвольное количество параметров.
f()
f(0)
f(0, 1)
f(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
len(args)=0, args=()
len(args)=1, args=(0,)
len(args)=2, args=(0, 1)
len(args)=11, args=(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
Все переданные в функцию значения попадают в кортеж, который связывается с именем args
.
def g(*args):
print(type(args))
g()
g(0)
g(0, 1)
<class 'tuple'>
<class 'tuple'>
<class 'tuple'>
Полный синтаксис допускает наличие обычных позиционных параметров и одного перехватывающего.
def f(pos1, ..., posN, *args):
...
Тогда в перехватывающий параметр попадают все оставшиеся аргументы.
def f(x, *args):
print(f"{x=}, {args=}")
f(0)
f(0, 1)
f(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
x=0, args=()
x=0, args=(1,)
x=0, args=(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
Функция print — пример функции, объявленной таким образом. Действительно, мы можем напечатать произвольное количество объектов за один вызов функции print
.
print(0)
print()
print(0, 1)
print(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
0
0 1
0 1 2 3 4 5 6 7 8 9 10
Функции min и max — ещё два примера таких функций. Если в любую из них передать два и более аргумента, то результатом будет минимальное или максимальное значение из всех аргументов.
print(min(0, 1, 3, -1, 2))
print(max(0, 1, 3, -1, 2))
-1
3
Распаковка последовательности по позиционным аргументам функции#
Допустима в некотором смысле и обратная операция к перехвату всех позиционных аргументов, которая позволяет распаковать последовательность произвольной длины по позиционным аргументам функции. Для этого при передаче распаковываемого аргумента перед ним ставится “*
”. Если f
— вызываемая функция, s
— распаковываемая последовательность, то используется следующий синтаксис.
f(..., *s, ...)
Сравните следующие два вызова функции print
.
r = range(3)
print(r)
print(*r)
range(0, 3)
0 1 2
Такое синтаксис будет работать с любой последовательностью.
s = "abc"
l = [0, 1, 2]
print(s, sep=", ")
print(*s, sep=", ")
print(l)
print(*l)
abc
a, b, c
[0, 1, 2]
0 1 2
Естественно это работает и с пользовательскими функциями.
def f(x, y, z):
print(f"{x=}, {y=}, {z=}")
x, y, z = range(3)
f(x, y, z)
f(*range(3))
x=0, y=1, z=2
x=0, y=1, z=2
Перехват произвольного количества переданных по имени аргументов#
Чтобы перехватить произвольное количество переданных по имени параметров, используется схожий синтаксис, но вместо одного символа “*
” перед перехватывающим параметром, ставится сразу два. Такому параметру обычно дают имя **kwargs
(сокращение от key words arguments
).
Ниже приведен пример такого объявления.
def f(**kwargs):
print(f"{len(kwargs)=}, {kwargs=}")
f()
f(x=1, y=2)
f(first_name="Иван", surname="Иванов", age=18)
len(kwargs)=0, kwargs={}
len(kwargs)=2, kwargs={'x': 1, 'y': 2}
len(kwargs)=3, kwargs={'first_name': 'Иван', 'surname': 'Иванов', 'age': 18}
В этом случае все переданные аргументы группируются в словаре. В качестве ключей выступают имена параметров, в качестве значений — переданные аргументы.
def g(**kwargs):
print(type(kwargs))
g()
<class 'dict'>
Естественно можно комбинировать перехват позиционных и именованных параметров.
def f(*args, **kwargs):
print(f"{args=}, {kwargs=}")
f("a", 1, letter="b", digit=2)
args=('a', 1), kwargs={'letter': 'b', 'digit': 2}
Распаковка словарей по аргументам функции#
Словари тоже допускают распаковку по параметрам функции, но при распаковке элементы распределяются не по позициям, а по именам: ключи словаря выступают в качестве имён параметров, а значения — в качестве аргументов. В этом случае при вызове функции перед распаковываемым словарем ставится два символа “*
”. Если f
— вызываемая функция, а d
— распаковываемый словарь, то используется следующий синтаксис.
f(..., **d, ...)
def f(x, y):
print(f"{x=}, {y=}")
d = {"x": 1, "y": 2}
f(**d)
x=1, y=2
Естественно можно совмещать одновременную распаковку нескольких последовательностей и нескольких словарей, если у функции хватает параметров.
l1 = range(3)
l2 = [3, 4, 5]
d1 = {"sep": "->"}
d2 = {"end": " =)"}
print(*l1, *l2, **d1, **d2)
0->1->2->3->4->5 =)