Передача аргументов при вызове 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 шт):

Автор решения: Xuiz

**kwargs нельзя располагать до *args, будет ошибка.

Если вы передаёте *args и **kwargs одновременно, то *args обрабатывается перед **kwargs.

→ Ссылка
Автор решения: Xander

Не совсем понимаю, зачем использовать * для передачи аргументов, если можно просто перечислить аргументы без указателя?

Если вы аргументы явно в коде пишете руками, то, действительно, нет смысла использовать распаковку через звёздочку.

Но если у вас набор аргументов формируется в другом месте кода и заранее не известно, сколько аргументов там будет, то без звёздочки вы не обойдётесь.

Пример:

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)
→ Ссылка
Автор решения: CrazyElf

* "распаковывает" 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. Это даёт очень большую гибкость при передаче и обработке аргументов.

→ Ссылка
Автор решения: Vitalizzare

Ответ на первый вопрос "Зачем использовать * для передачи аргументов" частично дан ранее, а именно: список аргументов может зависеть от контекста, в котором выполняется задача. Дополню его такими примерами:

  1. Вы можете работать со списком как некой единой сущностью. Например, это точка point в трехмерном пространстве. А функция f требует отдельно три координаты в качестве аргументов. И тогда перед вами две альтернативы: либо вводить новые переменные x, y, z = point; f(x, y, z), либо распаковать точку при вызове функции f(*point).

  2. Вы хотите, чтобы функция работала не с объектами, а их составными частями. Например, у вас 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))

  3. Вам может понадобиться передать настройки, которые удобнее хранить в виде словаря, а не отдельных переменных. Например, при чтении 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, упомянутый в последнем предложении цитаты.

→ Ссылка