Функции — это жизнь. Правда? Если вы только начали осваивать Python, неважно — первый ли это ваш язык программирования, или вы пришли в Python из другого языка, то вы уже знаете о том, что количество параметров в объявлении функции соответствует количеству аргументов, которые передают функции при вызове.

Это — основы. Это то, что помогает людям понимать окружающий мир. Но утверждение «количество параметров равно количеству аргументов» закладывает в голову новичка бомбу замедленного действия, которая срабатывает после того, как он увидит в объявлении функции таинственные конструкции
Не позволяйте всяким значкам загонять себя в ступор. Тут нет ничего архисложного. В общем-то, если эти конструкции вам незнакомы — предлагаю с ними разобраться.
Для того чтобы разобраться с
Сначала поговорим о том, чем они отличаются. В простейшей функции мы просто сопоставляем позиции аргументов и параметров. Аргумент №1 соответствует параметру №1, аргумент №2 — параметру №2 и так далее.
Для вызова функции необходимы все три аргумента. Если пропустить хотя бы один из них — будет выдано сообщение об ошибке.
Если при объявлении функции назначить параметру значение по умолчанию — указывать соответствующий аргумент при вызове функции уже необязательно. Параметр становится опциональным.
Опциональные параметры, кроме того, можно задавать при вызове функции, используя их имена.
В следующем примере установим три параметра в значение по умолчанию
Оператор
Этот оператор позволяет «распаковывать» объекты, внутри которых хранятся некие элементы. Вот пример:
Тут берётся содержимое списка
Итак, мы знаем о том, что оператор «звёздочка» в Python способен «вытаскивать» из объектов составляющие их элементы. Знаемы мы и о том, что существует два вида параметров функций. Вполне возможно, что вы уже додумались до этого сами, но я, на всякий случай, скажу об этом. А именно,
Каждая из этих конструкций используется для распаковки аргументов соответствующего типа, позволяя вызывать функции со списком аргументов переменной длины. Например — создадим функцию, которая умеет выводить результаты, набранные учеником в тесте:
Я не использовал при объявлении функции конструкцию
После того, как мы разобрались с
Вот несколько советов, которые помогут вам избежать распространённых проблем, возникающих при работе с функциями, и расширить свои знания:
Уважаемые читатели! Какими параметрами вы чаще всего пользуетесь при объявлении Python-функций?

Это — основы. Это то, что помогает людям понимать окружающий мир. Но утверждение «количество параметров равно количеству аргументов» закладывает в голову новичка бомбу замедленного действия, которая срабатывает после того, как он увидит в объявлении функции таинственные конструкции
*args
или **kwargs
.Не позволяйте всяким значкам загонять себя в ступор. Тут нет ничего архисложного. В общем-то, если эти конструкции вам незнакомы — предлагаю с ними разобраться.
Позиционные и именованные аргументы
Для того чтобы разобраться с
*args
и **kwargs
, нам нужно освоить концепции позиционных (positional) и именованных (keyword) аргументов.Сначала поговорим о том, чем они отличаются. В простейшей функции мы просто сопоставляем позиции аргументов и параметров. Аргумент №1 соответствует параметру №1, аргумент №2 — параметру №2 и так далее.
def printThese(a,b,c):
print(a, "is stored in a")
print(b, "is stored in b")
print(c, "is stored in c")
printThese(1,2,3)
"""
1 is stored in a
2 is stored in b
3 is stored in c
"""
Для вызова функции необходимы все три аргумента. Если пропустить хотя бы один из них — будет выдано сообщение об ошибке.
def printThese(a,b,c):
print(a, "is stored in a")
print(b, "is stored in b")
print(c, "is stored in c")
printThese(1,2)
"""
TypeError: printThese() missing 1 required positional argument: 'c'
"""
Если при объявлении функции назначить параметру значение по умолчанию — указывать соответствующий аргумент при вызове функции уже необязательно. Параметр становится опциональным.
def printThese(a,b,c=None):
print(a, "is stored in a")
print(b, "is stored in b")
print(c, "is stored in c")
printThese(1,2)
"""
1 is stored in a
2 is stored in b
None is stored in c
"""
Опциональные параметры, кроме того, можно задавать при вызове функции, используя их имена.
В следующем примере установим три параметра в значение по умолчанию
None
и взглянем на то, как их можно назначать, используя их имена и не обращая внимания на порядок следования аргументов, применяемых при вызове функции.def printThese(a=None,b=None,c=None):
print(a, "is stored in a")
print(b, "is stored in b")
print(c, "is stored in c")
printThese(c=3, a=1)
"""
1 is stored in a
None is stored in b
3 is stored in c
"""
Оператор «звёздочка»
Оператор
*
чаще всего ассоциируется у людей с операцией умножения, но в Python он имеет и другой смысл.Этот оператор позволяет «распаковывать» объекты, внутри которых хранятся некие элементы. Вот пример:
a = [1,2,3]
b = [*a,4,5,6]
print(b) # [1,2,3,4,5,6]
Тут берётся содержимое списка
a
, распаковывается, и помещается в список b
.Как пользоваться *args и **kwargs
Итак, мы знаем о том, что оператор «звёздочка» в Python способен «вытаскивать» из объектов составляющие их элементы. Знаемы мы и о том, что существует два вида параметров функций. Вполне возможно, что вы уже додумались до этого сами, но я, на всякий случай, скажу об этом. А именно,
*args
— это сокращение от «arguments» (аргументы), а **kwargs — сокращение от «keyword arguments» (именованные аргументы).Каждая из этих конструкций используется для распаковки аргументов соответствующего типа, позволяя вызывать функции со списком аргументов переменной длины. Например — создадим функцию, которая умеет выводить результаты, набранные учеником в тесте:
def printScores(student, *scores):
print(f"Student Name: {student}")
for score in scores:
print(score)
printScores("Jonathan",100, 95, 88, 92, 99)
"""
Student Name: Jonathan
100
95
88
92
99
"""
Я не использовал при объявлении функции конструкцию
*args
. Вместо неё у меня — *scores
. Нет ли тут ошибки? Ошибки здесь нет. Дело в том, что «args» — это всего лишь набор символов, которым принято обозначать аргументы. Самое главное тут — это оператор *
. А то, что именно идёт после него, особой роли не играет. Благодаря использованию *
мы создали список позиционных аргументов на основе того, что было передано функции при вызове.После того, как мы разобрались с
*args
, с пониманием **kwargs
проблем быть уже не должно. Имя, опять же, значения не имеет. Главное — это два символа **
. Благодаря им создаётся словарь, в котором содержатся именованные аргументы, переданные функции при её вызове.def printPetNames(owner, **pets):
print(f"Owner Name: {owner}")
for pet,name in pets.items():
print(f"{pet}: {name}")
printPetNames("Jonathan", dog="Brock", fish=["Larry", "Curly", "Moe"], turtle="Shelldon")
"""
Owner Name: Jonathan
dog: Brock
fish: ['Larry', 'Curly', 'Moe']
turtle: Shelldon
"""
Итоги
Вот несколько советов, которые помогут вам избежать распространённых проблем, возникающих при работе с функциями, и расширить свои знания:
- Используйте общепринятые конструкции
*args
и**kwargs
для захвата позиционных и именованных аргументов. - Конструкцию
**kwarg
s нельзя располагать до*args
. Если это сделать — будет выдано сообщение об ошибке. - Остерегайтесь конфликтов между именованными параметрами и
**kwargs
, в случаях, когда значение планируется передать как**kwarg
-аргумент, но имя ключа этого значения совпадает с именем именованного параметра. - Оператор
*
можно использовать не только в объявлениях функций, но и при их вызове.
Уважаемые читатели! Какими параметрами вы чаще всего пользуетесь при объявлении Python-функций?

Комментарии (7)
Pavel1114
11.01.2020 05:34Отрывок очень не точной и поверхностной перепечатки документации — второе место в топе Хабра. Согласен что для совсем новичков в программировании статья может показаться полезной. Но блин на хабре я ожидаю более глубокие статьи. Может я не прав, может хабр для всех…
SantaCluster
12.01.2020 12:57лично я ожидаю, что если статья написана для новичков (и даже для новичков-новичков), то профессионалы либо не станут читать или комментировать (особенно в духе выражения своего "Фи!") либо прокомментируют по существу — укажут на ошибку или добавят интересный случай/пример из практики
dark_gf
Я не понимаю поведения в питоне
1)Если по определению a = [1,2,3] и *a = 1,2,3 (пишу образно для понимания моего не понимания :) ), то в функции printScores(student, *scores) когда вызываем printScores(«Jonathan», 100, 95, 88, 92, 99) получается мы делам обратную операцию *, от всех «следующих аргументов» и тут возникает вопрос, почему не от всех аргументов, а только от «последних», а что если я хочу такую функцию printScores(student, *scores, *otherData)?
И вообще почему нельзя сделать так *a = [1,2,3], что бы получить a = [[1,2,3]]?
2)def printPetNames(owner, **pets) — тут двойная звездочка, в моём понимании по определению это двойная операция *, т.е. если **pets = dog=«Brock», fish=[«Larry», «Curly», «Moe»], turtle=«Shelldon», то *pets = {dog=«Brock», fish=[«Larry», «Curly», «Moe»], turtle=«Shelldon»]} а pets = [{dog=«Brock», fish=[«Larry», «Curly», «Moe»], turtle=«Shelldon»]}]
имхо оператор * имеет только похожее поведение на то что используется в функциях (*args и **kwargs)
tolord
1) Если ты напишешь
print_scores(arg1, arg2, ..., argN, *scores)
, предполагается, что у тебя N аргументов обязательные, а остальные опциональные, и эти остальные соберутся в отдельный кортеж. То есть конструкцияfunc(arg, *args1, *args2)
невозможна, потому что непонятно, как поделить остальные переменные междуargs1
иargs2
. Если тебе хочется получить два списка аргументов, лучше явно запихни их в списки (fun(arg1=(1, 2, 3), arg2=(4, 5, 6))
)Писать
*a = [1, 2, 3]
нельзя, потому что * это операция распаковки, а не обращение к указателю как в C++. Даже если предположить, что такая конструкция имела бы место, это было бы сильно не pythonic way, потому что гораздо проще и понятнее то, что ты написал:a = [[1, 2, 3]]
.2) Двойная звёздочка это не двойная распаковка. Это сборка именованных параметров в словарь и наоборот. Одинарная звёздочка на словарь вернёт тебе список ключей. Например,
напечатает на экране:
kek, lol, cheburek
. Логика «двойной оператор это то же самое, что два одинарных» не работает, потому что есть же разница между=
и==
.UPD: Коллега ниже отметил, что звезда собирает аргументы в кортеж, исправил оговорку.
MonstarNN
В описании функции не допускается многократное использование позиционных и именованных аргументов, то есть такое описание функции, как printScores(student, *scores, *otherData), или же printScores(student, **kwargs, **other_kwargs) недопустимо — интерпретатор упадёт с ошибкой «invalid syntax». Оно и понятно — если более одного набора позиционных или именованных аргументов — нет возможности однозначно поделить аргументы между ними.
Позиционные аргументы приходят в виде кортежа (tuple), который как раз определяется звёздочкой, именованные аргументы приходят в виде словаря dict, и двойная звёздочка тут есть именно задание словаря. То есть при описании функции звёздочки это не операторы, выполняющие какое-либо действие, а именно описание параметров функции, так для printScores(student, *args, **kwargs) мы указываем, что внутри функции мы будем работать с кортежем args и словарём kwargs, по которым будут «разбросаны» все позиционные и именованные аргументы вызова функции. И чтобы не было путаницы, в вызове сначала указываются позиционные параметры (попадают в кортеж args с сохранением последовательности), а потом именованные, то есть вызов printScores('name', param='value', 42) недопустим.
При вызове printScores('name', [1,2,3]) в scores будет кортеж ([1,2,3]), ну или, если кортеж преобразовать в список, то будет список: list(scores) = [[1,2,3]]
Pand5461
Дело в том, что звёздочки — это не процедуры, а часть синтаксиса. Смысл звёздочек в определении процедуры и в выражении, вызывающем процедуру — разный.
*x
в списке формальных аргументов — обозначает, что принимается произвольное число аргументов, которые в теле процедуры будут доступны в кортеже под именемx
. (slurp)*x
в вызове процедуры — обозначает, что коллекцияx
распаковывается подставляется как отдельные аргументы. (splat)Соответственно, если
a = [1, 2, 3]
, то*a
вне контекста вызова процедуры — это синтаксическая ошибка.