Путаница с функциями Python, использующими аргумент, аргумент ключевого слова, *args, **kwargs

Учитывая приведенную ниже функцию и получающиеся вызовы print_stuff()Может кто-нибудь объяснить, почему происходит непредвиденное поведение при вызове функции без ключевого слова по умолчанию, но передачи в список *args?

Я знаю, что есть "ошибка", связанная с изменяемыми / неизменяемыми значениями по умолчанию для ключевых слов, но я не вижу, чтобы это было здесь уместно.

Может кто-то уточнить, почему это происходит, или любая ошибка синтаксиса / вызова?

def print_stuff(arg, kwarg=None, *args):
    print "arg: %s" % arg

    if kwarg:
        print "kwarg: %s" % kwarg

    if args:
        for a in args:
            print "printing {} from args".format(a)

    print "===end==="

args_list = range(1, 3)
kwargs_list = {str(a):a for a in args_list}

print_stuff('hi', kwarg='some_kwarg') # works as intended

print_stuff('hi', 'some_kwarg', *range(1, 3)) # also works as intended

print_stuff('hi', *range(1, 3)) # first element of list unexpectedly passed in to keyword argument, even using the * notation

print_stuff('hi', kwarg='some_kwarg', *range(1, 3)) # TypeError: print_stuff() got multiple values for keyword argument 'kwarg'

4 ответа

Решение

Помимо наличия значения по умолчанию для kwarg, print_stuff(arg, kwarg=None, *args) ничем не отличается от print_stuff(arg, kwarg, *args) - если есть второй позиционный аргумент, он передается как kwarg параметр (любые последующие позиционные аргументы заканчиваются в args).

Обратите внимание, что:

print_stuff('hi', *range(1, 3))

оценивается как:

print_stuff('hi', 1, 2)

поэтому первый аргумент идет к argвторой идет kwarg и третий args[0],


Тогда, если мы заметим, что:

print_stuff('hi', kwarg='some_kwarg', *range(1, 3))

эквивалентно:

print_stuff('hi', *range(1, 3), kwarg='some_kwarg')

и, следовательно, чтобы:

print_stuff('hi', 1, 2, kwarg='some_kwarg')

Вы можете увидеть проблему - снова 'hi', 1 а также 2 идти к arg, kwarg а также args[0] соответственно, но тогда другое значение для kwarg неожиданно появляется


Если вы хотите, чтобы все позиционные аргументы были "впитаны" раньше kwarg считается, измените определение функции на:

def print_stuff(arg, *args, **kwargs):
    print "arg: %s" % arg
    kwarg = kwargs.get('kwarg')
    if kwarg is not None:  # note explicit test - what if kwarg == 0?
        print "kwarg: %s" % kwarg

    for a in args:  # no need to test here - loop doesn't run if no args
        print "printing {} from args".format(a)

    print "===end==="

В использовании:

>>> print_stuff('hi', *range(1, 3))
arg: hi
printing 1 from args
printing 2 from args
===end===
>>> print_stuff('hi', *range(1, 3), kwarg='some_kwarg')
arg: hi
kwarg: some_kwarg
printing 1 from args
printing 2 from args
===end===

Обратите внимание, что в 3.x вы можете иметь аргументы только для ключевых слов, то есть следующее является альтернативой:

def print_stuff(arg, *args, kwarg=None):
    ...

kwarg не аргумент только для ключевого слова; это просто позиционный аргумент, который имеет значение по умолчанию. Ваш звонок

print_stuff('hi', *range(1,3))

точно так же, как

print_stuff('hi', 1, 2)

который назначает первые два аргумента первым двум именованным параметрам, а остальные (т. е. третий) помещаются в *args параметр.

Один простой способ думать об этом:

Когда аргумент передается в функцию, он добавляется в своего рода позиционный аргумент "очередь". При назначении этой очереди Python будет игнорировать аргументы с ключевыми словами и назначать приоритеты аргументам без ключевых слов. Все ключевые аргументы назначаются последними в вызовах функций. Вы можете представить это как Python, "изменяющий порядок" ваших аргументов, потому что он стремится заполнить позиции в первую очередь. Так с звонком вроде:

print_stuff('hi', kwarg='some_kwarg', *range(1,3))

Python в основном преобразует его в:

print_stuff('hi', 1, 2, kwarg='some_kwarg')

и тогда разозлится, потому что вы назначаете на kwarg дважды.

*** Обратите внимание, что это не обязательно то, что на самом деле происходит, но это хороший способ думать об этом, потому что вы сможете справиться с ошибками, а также сможете объяснить, почему в вызове, например:

print_stuff('hi', *range(1,3))

1 передается второму позиционному аргументу kwarg, а 2 передается третьему аргументу аргументов.

Просто я пишу функцию, чтобы показать, как *args а также **kwargs работает

def test_function(normal_arg, *arg, **kwargs):
    print(locals())
    return

test_function("normal_arg-value", 
    "*arg1-value", "*arg2-value",
    karg1="karg1-value", karg2="karg2-value")

#output
{'arg1': 'arg1-value', 'arg': ('*arg1-value', '*arg2-value'), 'kwargs': {'karg2': 'karg2-value', 'karg1': 'karg1-value'}}

Надеюсь это поможет.

Как неограниченное количество аргументов может передаваться функции

def test(*lis):
    print locals()

test(1,2,3)

#output
{'lis': (1, 2, 3)}

Как неограниченные именованные аргументы могут передаваться в функцию

def test(**dicts):
    print locals()

test(arg1="value1", arg2="value2")

#output
{'dicts': {'arg1': 'value1', 'arg2': 'value2'}}

Как словарь передается функции в качестве аргумента

def test(**dicts):
    print locals()

test(**{"arg1":"value1", "arg2": "value2"})

#output
{'dicts': {'arg1': 'value1', 'arg2': 'value2'}}

Надеюсь, это поможет вам

Другие вопросы по тегам