Статью я написал потому, что постоянно забываю как обращаться к аргументам функций в разных сложных случаях. Надоело гуглить — сделал себе шпаргалку. Для упрощения ситуации речь пойдет про python 3.x.
Основные правила
-
аргументы передаются через присваивание объектов именам локальных переменных функций
-
при передаче объекты никогда не копируются автоматически
-
присваивание аргументов внутри функции не затрагивает вызывающий код
-
при выполнении функции аргументы функции становятся ее локальными переменными в области видимости функции
-
имена аргументов функции и имена переменных в области видимости функции не совмещаются
-
модификация изменяемого объекта внутри функции может затронуть вызывающий код
В примере аргументы присвоен объект, на который указывает переменная из другого контекста. Внутри функции произошло присваивание локальной переменной нового объекта, что не затронуло вызывающий код:
def func(a):
print(a)
a = 100
print(a)
a = 10
func(a)
print(a)
10
100
10
В следующем примере видно, как изменение на месте изменяемого объекта внутри функции приводит к последствиям определенного рода.
def func(a):
print(a)
a.append('that')
print(a)
a = ['this']
func(a)
print(a)
['this']
['this', 'that']
['this', 'that']
Оператор return поддерживает возврат множественных значений, упаковывая их в коллекцию. Естественно, это можно распаковать в вызывающем коде. Например, так:
def func(a, b):
x = a + b
y = a - b
return a, b
result = func(5, 3)
print(result)
x, y = func(4, 2)
print(x, y)
(5, 3)
4 2
Режимы сопоставления аргументов
При объявлении функции:
-
обязательные аргументы будут сопоставляться слева направо с любыми переданными позиционными аргументами при вызове функции def func{имя)
-
стандартные значения, которые будут присвоены аргументу, если в вызове функции аргумент не передавался def func{имя=значение)
-
def func(*имя) — собирает произвольное количество оставшихся позиционных аргументов в кортеж
-
def func{**имя) — собирает произвольное количество оставшихся ключевых аргументов в словарь
-
def func(*остальные, имя) или def func(*, имя=значение) — аргументы, которые должны быть переданы только по ключевому слову
При вызове функции:
-
позиционные аргументы сопоставляются слева направо func{значение)
-
ключевые сопоставляются по имени аргумента func{имя=значение)
-
func(*итерируемый_объект) — передает все объекты в итерируемом объекте как отдельные позиционные аргументы
-
func{**словарь) — передает все объекты в итерируемом объекте как отдельные ключевые аргументы
При объявлении функции аргументы должны указываться в следующем порядке:
-
любые позиционные аргументы имя
-
любые стандартные аргументы имя=значение
-
форма *имя или *
-
аргументы, которые должны передаваться только по ключевым словам имя или имя=значение
-
форма **имя
При вызофе функции аргументы должны указываться в следующем порядке:
-
любые позиционные аргументы значение
-
комбинация любых ключевых аргументов имя=значение
-
*итерируемый_объект
-
**словарь
При несоблюдении указанного порядка выводится синтаксическая ошибка. При этом сопоставляться аргументы будут в следующем порядке:
-
Присваивание неключевых аргументов по позиции слева направо (порядок следования имеет значение, так как сопоставление осуществляется по позиции)
-
Присваивание ключевых аргументов по совпадающим именам (порядок следования аргументов не имеет значения, так как сопоставление происходит по имени, а не по позиции)
-
Присваивание оставшихся неключевых аргументов кортежу *имя
-
Присваивание оставшихся ключевых аргументов словарю **имя
-
Присваивание стандартных значений не присвоенным аргументам
-
Проверка, передается ли каждому аргументу только одно значение — если нет, тогда возникает ошибка
-
Присваивание именам аргументов переданных для них объектов
При этом учитывается то, какие из аргументов могут быть присвоены только по ключевым словам. Аргументы с передачей только по ключевым словам должны указываться после *, но не могут указываться после **аргументы. Также, конструкция ** не может появляться в списке аргументов сама по себе.
Примеры
def func(a, b=2, c=3):
pass
# a обязательный, остальные нет
# предоставим значение a по позиции
func(1)
# предоставим значение a по ключу
func(a=1)
# переопределим все три значения
func(4, 5, 6)
# передадим a и переопределим c
func(1, c=6)
# такой вариант вызовет ошибку
func(1, a=2)
# это тоже неверно
func(b=2, 1)
В данном примере все аргументы, которым присвоено стандартное значение, необязательны при вызове функции. Объекты можно передавать в функцию как по позиции, так и по ключу.
Примеры с произвольным количеством аргументов:
def func(*args):
print(args)
>>>func():
()
>>>func(1):
(1,)
>>>func(1, 2):
(1, 2)
Тоже для ключевых аргументов:
def func(**kwargs):
print(kwargs)
>>>func():
()
>>>func(a=l, b=2)):
{'a' : 1, 'b': 2}
Все вместе:
def func(a, *args, **kwargs):
print(a, args, kwargs)
>>>func():
()
>>>func(l, 2, 3, a=l, b=2):
1 (2, 3) {'a' : 1, 'b': 2}
Распаковка аргументов при вызове функции:
def func(a, b, c):
print(a, b, c)
>>>args = (1, 2, 3)
>>>func(*args)
1 2 3
>>>kwargs = {'a': 1, 'b': 2, 'c': 3}
>>>func(**kwargs)
1 2 3
Естественно можно сочетать позиционные, ключевые и агрегированные аргументы
def func(a, b, c, d, e):
print(a, b, c, d, e)
>>>func(1, *(2, 3), d=4, **{'e': 5})
1 2 3 4 5
Наконец, мы можем указать какие из аргументов могут быть использованы только как ключевые:
def func(a, *b, c):
print(a, b, c)
>>>func(1, 2, c=3)
1 (2,) 3
>>>func(1, 2, 3)
TypeError: func() missing 1 required keyword-only argument: 'c'
Кроме того, для аргументов «только по ключу» можно задавать стандартные значения:
def func(a, *b, c=3):
print(a, b, c)
Аргументы «только по ключу» должны быть записаны после формы с произвольным количеством позиционных аргументов *аргументы и перед формой с произвольным количеством ключевых аргументов **аргументы.
def func(a, *b, c=3, **d):
print(a, b, c, d)
>>>func(1, 2, 3, c=6, x=2, y=4)
1 (2, 3) 6 {'x': 2, 'y': 4}
def func(a, *b, **d, c=3):
SyntaxError: invalid syntax
print(a, b, c, d)
Важный аспект распаковки итерируемых аргументов в индивидуальные аргументы заключается в том, что есть возможность использовать генераторы в качестве аргументов. В данном случае мы распаковали значения генераторного выражения:
def func(a, b, c):
print(a, b, c)
>>>func(*(i for i in range(3)))
0 1 3
Еще один фокус — распаковка итератора ключей:
>>>some_dict = dict(a=1, b=2, c=3)
# распаковка словаря
>>>func(**some_dict)
1 2 3
# распаковка итератора ключей
>>>func(*some_dict)
a b c
# или так
>>>func(*some_dict.values())
1 2 3
Изменения в Python 3.8
В python 3.8 добавлена возможность указывать аргументы, которые могут быть переданы только как позиционные. Это выполняется с помощью маркера / который указывает, что все аргументы слева от него передаются исключительно как позиционные.
def func(a, b, c=3, /):
print(a, b, c)
>>>func(1, 2, 3)
1 2 3
>>>func(1, 2, c=3)
TypeError
Статья подготовлена на основе книги «Изучаем Python» Lutz, Mark