Передача аргументов при вызове python
Читая документацию, нашел реализацию вызова метода с передачей аргументов.
def printArgs(a, b):
print(f"a = {a}, b = {b}")
printArgs(*(2,3))
printArgs(2,3)
Не совсем понимаю, зачем использовать *
для передачи аргументов, если можно просто перечислить аргументы без указателя?
Также, как я понял, передача starred_and_keywords
происходит первой, поэтому передача аргумента b
, с помощью указателя, выводит ошибку:
printArgs(a=1, *(2,))
Traceback (most recent call last):
File "C\...", line 4, in <module>
printArgs(a=1, *(2,))
TypeError: printArgs() got multiple values for argument 'a'
Ответы (4 шт):
**kwargs
нельзя располагать до *args
, будет ошибка.
Если вы передаёте *args
и **kwargs
одновременно, то *args
обрабатывается перед **kwargs
.
Не совсем понимаю, зачем использовать * для передачи аргументов, если можно просто перечислить аргументы без указателя?
Если вы аргументы явно в коде пишете руками, то, действительно, нет смысла использовать распаковку через звёздочку.
Но если у вас набор аргументов формируется в другом месте кода и заранее не известно, сколько аргументов там будет, то без звёздочки вы не обойдётесь.
Пример:
args = []
if some_conditions_1:
args.append(arg1)
if some_conditions_2:
args.append(arg2)
if some_condition_3:
args.append(arg3)
my_func(*args)
*
"распаковывает" iterable
, поэтому да, получается:
printArgs(*(2,3))
# превращается в
printArgs(2,3)
Это удобно, если вы распаковываете не константный кортеж (тогда действительно в записи со звёздочкой нет смысла), а генератор, список или кортеж с переменными и т.п.
Фактически, если некая функция описана так def func(*args, **kwargs)
, то происходит "двойная магия", если вы передаёте в неё аргументы при вызове так же через func(*args, **kwargs)
.
Допустим:
args = [1, 2, 3]
kwargs = {'a': 5, 'b': 6, 'c': 7}
Тогда:
func(*args, **kwargs)
# превращается
func(1, 2, 3, a=5, b=6, c=7)
При этом внутри самой функции происходит обратная упаковка:
def func(*args, **kwargs):
# тут
# args = [1, 2, 3]
# kwargs = {'a': 5, 'b': 6, 'c': 7}
Зачем это всё нужно? Для универсальности. Вы можете передать позиционные и именованные переменные как в явном виде, просто их перечислив, так и "распаковав" их (или часть из них) из iterable
(args
) и/или словаря (kwargs
).
И тоже самое в самой функции. Вы можете описать аргументы функции в явном виде, перечислив их, а можете принять на вход аргументы, "упакованные" в *args
и **kwargs
. Это даёт очень большую гибкость при передаче и обработке аргументов.
Ответ на первый вопрос "Зачем использовать *
для передачи аргументов" частично дан ранее, а именно: список аргументов может зависеть от контекста, в котором выполняется задача. Дополню его такими примерами:
Вы можете работать со списком как некой единой сущностью. Например, это точка
point
в трехмерном пространстве. А функцияf
требует отдельно три координаты в качестве аргументов. И тогда перед вами две альтернативы: либо вводить новые переменныеx, y, z = point; f(x, y, z)
, либо распаковать точку при вызове функцииf(*point)
.Вы хотите, чтобы функция работала не с объектами, а их составными частями. Например, у вас 2 слова
a = 'one'; b = 'two'
и вы хотите записать все буквы через запятую, используяf = lambda *args: ','.join(args)
. Вызовf(a, b)
вернет эти же слова разделенные запятой. А чтобы получить желаемое, вам понадобиться распаковать слова:f(*a, *b)
. Другой похожий пример - транспонирование списка списков с помощьюzip
:a = [[1,2,3], [4,5,6]] transposed = list(zip(*a))
Вам может понадобиться передать настройки, которые удобнее хранить в виде словаря, а не отдельных переменных. Например, при чтении CSV-файла вы можете составить свой словарь настроек
settings = {'delimiter': '|', 'lineterminator': ','}
и распаковать его при вызовеcsv.reader(file, **settings)
.
Что касается второго вопроса "TypeError: printArgs() got multiple values for argument 'a'", то он описан в следующем параграфе документации:
If keyword arguments are present, they are first converted to positional arguments, as follows. First, a list of unfilled slots is created for the formal parameters. If there are N positional arguments, they are placed in the first N slots. Next, for each keyword argument, the identifier is used to determine the corresponding slot (if the identifier is the same as the first formal parameter name, the first slot is used, and so on). If the slot is already filled, a
TypeError
exception is raised.
Другими словами, при вызове printArgs(a=1, *(2,))
позиционные параметры сначала будут заполняться из кортежа (2,)
, после этого именованная переменная a=1
будет позиционирована относительно порядка задекларированных параметров. И так как она попадает в первую позицию, которая уже занята двойкой, то возникает TypeError
, упомянутый в последнем предложении цитаты.