Правильный способ использования ** kwargs в Python


454

Как правильно использовать **kwargsв Python, когда речь идет о значениях по умолчанию?

kwargsвозвращает словарь, но каков наилучший способ установить значения по умолчанию, или он есть? Должен ли я просто получить доступ к нему как словарь? Использовать функцию get?

class ExampleClass:
    def __init__(self, **kwargs):
        self.val = kwargs['val']
        self.val2 = kwargs.get('val2')

Простой вопрос, но на который я не могу найти хорошие ресурсы. Люди делают это по-разному в коде, который я видел, и трудно понять, что использовать.

Ответы:


468

Вы можете передать значение по умолчанию get()для ключей, которых нет в словаре:

self.val2 = kwargs.get('val2',"default value")

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

def __init__(self, val2="default value", **kwargs):

16
Мне нравится использовать позиционные аргументы только для обязательных аргументов и kwargs для аргументов, которые могут или не могут быть указаны, но полезно иметь значение по умолчанию. Kwargs хорош тем, что вы можете отправлять свои аргументы в любом порядке по вашему выбору. Позиционные аргументы не дают вам этой свободы.
Кекоа

95
Вы можете передавать именованные аргументы в любом порядке. Вам нужно только придерживаться позиций, если вы не используете имена - что в случае с kwargs, вы должны. Скорее, использование именованных аргументов в отличие от kwargs дает вам дополнительную свободу не использовать имена - тогда, однако, вы должны соблюдать порядок.
Балфа

13
@Kekoa: вы всегда можете представить именованные аргументы в любом порядке по вашему выбору. Вам не нужно использовать ** kwargs, чтобы получить эту гибкость.
С.Лотт

13
Pylint помечает это как плохую форму, чтобы использовать kwargs в __init__(). Может кто-нибудь объяснить, почему это преступление, достойное ворса?
Hughdbrown


271

Хотя большинство ответов говорят, что, например,

def f(**kwargs):
    foo = kwargs.pop('foo')
    bar = kwargs.pop('bar')
    ...etc...

такой же как"

def f(foo=None, bar=None, **kwargs):
    ...etc...

это неправда. В последнем случае fможет быть вызвано как f(23, 42), тогда как в первом случае принимаются только именованные аргументы - никаких позиционных вызовов. Часто вы хотите позволить вызывающей стороне максимальную гибкость, и поэтому вторая форма, как утверждают большинство ответов, предпочтительнее, но это не всегда так. Когда вы принимаете много необязательных параметров, из которых обычно передаются только несколько, может оказаться отличной идеей (избегая аварий и нечитаемого кода на ваших сайтах вызовов!) Принудительно использовать именованные аргументы - threading.Threadпример. Первая форма - это то, как вы реализуете это в Python 2.

Эта идиома настолько важна, что в Python 3 теперь он имеет специальный поддерживающий синтаксис: каждый аргумент после единственного *в defподписи является ключевым словом, то есть не может быть передан как позиционный аргумент, а только как именованный. Таким образом, в Python 3 вы можете написать код выше:

def f(*, foo=None, bar=None, **kwargs):
    ...etc...

Действительно, в Python 3 вы даже можете иметь аргументы только для ключевых слов, которые не являются необязательными ( аргументы без значения по умолчанию).

Однако в Python 2 впереди долгие годы продуктивной жизни, поэтому лучше не забывать методы и идиомы, которые позволяют реализовывать в Python 2 важные идеи проектирования, которые непосредственно поддерживаются в языке Python 3!


10
@ Алекс Мартелли: Я не нашел ни одного ответа, который бы утверждал, что kwargs идентичны именованным аргументам, не говоря уже о превосходстве. Но хорошая беседа об изменениях Py3k, так что +1
Балфа

1
@ Алекс Мартелли: большое спасибо за ваш ответ, я узнал, что python 3 допускает обязательные аргументы-ключевые слова, отсутствие которых часто приводило к неудовлетворительной архитектуре в моем коде и функциях.
FloW

78

Я предлагаю что-то подобное

def testFunc( **kwargs ):
    options = {
            'option1' : 'default_value1',
            'option2' : 'default_value2',
            'option3' : 'default_value3', }

    options.update(kwargs)
    print options

testFunc( option1='new_value1', option3='new_value3' )
# {'option2': 'default_value2', 'option3': 'new_value3', 'option1': 'new_value1'}

testFunc( option2='new_value2' )
# {'option1': 'default_value1', 'option3': 'default_value3', 'option2': 'new_value2'}

А затем используйте значения любым способом

dictionaryA.update(dictionaryB)добавляет содержимое dictionaryBк dictionaryAперезаписи любых дубликатов ключей.


2
Спасибо @AbhinavGupta! Именно то, что я искал. Просто добавил, for key in options: self.__setattr__(key, options[key])и я готов идти. :)
propjk007

53

Ты бы сделал

self.attribute = kwargs.pop('name', default_value)

или

self.attribute = kwargs.get('name', default_value)

Если вы используете pop, то вы можете проверить, отправлены ли какие-либо ложные значения, и предпринять соответствующие действия (если таковые имеются).


2
Можете ли вы уточнить, что вы имеете в виду, говоря, .popчто это поможет вам «проверить, отправлены ли какие-либо ложные значения»?
Алан Х.

13
@ Алан Х .: если в kwargs что-то осталось после того, как все всплыли, то у вас есть ложные значения.
Vinay Sajip

@VinaySajip: Хорошо, это отличный момент для .pop "vs" .get, но я до сих пор не понимаю, почему pop предпочтительнее по сравнению с именованными аргументами, кроме принуждения вызывающей стороны не использовать позиционные параметры.
MestreLion

1
@MestreLion: это зависит от того, сколько ключевых слов аргументов ваш API позволяет. Я не утверждаю, что мое предложение лучше, чем именованные аргументы, но Python позволяет захватывать безымянные аргументы kwargsпо определенной причине.
Vinay Sajip

Итак, просто проверяю. Возвращает ли pop значение словаря, если ключ существует, а если нет, то возвращаетdefault_value переданное значение? И удаляет этот ключ потом?
Даниэль Мёллер

43

Использовать ** kwargs и значения по умолчанию легко. Однако иногда вам не следует использовать ** kwargs.

В этом случае мы не особо используем ** kwargs.

class ExampleClass( object ):
    def __init__(self, **kwargs):
        self.val = kwargs.get('val',"default1")
        self.val2 = kwargs.get('val2',"default2")

Выше это "зачем?" декларация. Это так же, как

class ExampleClass( object ):
    def __init__(self, val="default1", val2="default2"):
        self.val = val
        self.val2 = val2

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

Когда вы используете ** kwargs, вы обычно подразумеваете что-то более похожее на следующее, где простые значения по умолчанию не применяются.

class ExampleClass( object ):
    def __init__(self, **kwargs):
        self.val = "default1"
        self.val2 = "default2"
        if "val" in kwargs:
            self.val = kwargs["val"]
            self.val2 = 2*self.val
        elif "val2" in kwargs:
            self.val2 = kwargs["val2"]
            self.val = self.val2 / 2
        else:
            raise TypeError( "must provide val= or val2= parameter values" )

Мне нравится этот маленький головоломка! Я продолжал думать: «Но вы можете просто использовать get или pop с - о, они взаимозависимы ...»
trojjer

28

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

class Exampleclass(object):
  def __init__(self, **kwargs):
    for k in kwargs.keys():
       if k in [acceptable_keys_list]:
          self.__setattr__(k, kwargs[k])

да, это элегантно и мощно ... не слишком уверенно насчет квадратных скобок вокруг accept_keys_list, хотя: я бы сделал это кортежем или списком, а затем убрал бы эти скобки в операторе "if"
Майк Родент

Я немного изменил это для случаев, когда ожидаются все ключи: stackoverflow.com/questions/1098549/…
rebelliard

14

Вот еще один подход:

def my_func(arg1, arg2, arg3):
    ... so something ...

kwargs = {'arg1': 'Value One', 'arg2': 'Value Two', 'arg3': 'Value Three'}
# Now you can call the function with kwargs like this:

my_func(**kwargs)

Много использовал в Django CBV (например get_form_kwargs()). ccbv.co.uk/projects/Django/1.5/django.views.generic.edit/…
trojjer

В этом get_form()методе показано, как получить аргументы ключевого слова путем переноса на другой метод ( get_form_kwargsкак упомянуто выше). Он конкретизирует форму следующим образом : form_class(**self.get_form_kwargs()).
Trojjer

Тогда легко переопределить get_form_kwargs()в представлении подкласса и добавить / удалить kwargs на основе определенной логики. Но это для учебника по Django.
Trojjer

12

Я думаю, что правильный способ использовать **kwargsв Python, когда дело доходит до значений по умолчанию, это использовать метод словаря setdefault, как указано ниже:

class ExampleClass:
    def __init__(self, **kwargs):
        kwargs.setdefault('val', value1)
        kwargs.setdefault('val2', value2)

Таким образом, если пользователь передает ключевое слово «val» или «val2» args, они будут использоваться; в противном случае будут использоваться значения по умолчанию, которые были установлены.


11

Вы могли бы сделать что-то вроде этого

class ExampleClass:
    def __init__(self, **kwargs):
        arguments = {'val':1, 'val2':2}
        arguments.update(kwargs)
        self.val = arguments['val']
        self.val2 = arguments['val2']

11

Следуя предложению @srhegde об использовании setattr :

class ExampleClass(object):
    __acceptable_keys_list = ['foo', 'bar']

    def __init__(self, **kwargs):
        [self.__setattr__(key, kwargs.get(key)) for key in self.__acceptable_keys_list]

Этот вариант полезен, когда ожидается, что у класса будут все элементы в нашем acceptableсписке.


1
Это не тот случай использования для понимания списка, вы должны использовать цикл for в вашем методе init.
эттананы

5

Если вы хотите объединить это с * args, вы должны оставить * args и ** kwargs в конце определения.

Так:

def method(foo, bar=None, *args, **kwargs):
    do_something_with(foo, bar)
    some_other_function(*args, **kwargs)

1

@AbhinavGupta и @Steef предложили использовать update(), что я нашел очень полезным для обработки больших списков аргументов:

args.update(kwargs)

Что если мы хотим проверить, что пользователь не передал никаких ложных / неподдерживаемых аргументов? @VinaySajip указал, что pop()может использоваться для итеративной обработки списка аргументов. Тогда любые оставшиеся аргументы являются ложными. Ницца.

Вот еще один возможный способ сделать это, который сохраняет простой синтаксис использования update():

# kwargs = dictionary of user-supplied arguments
# args = dictionary containing default arguments

# Check that user hasn't given spurious arguments
unknown_args = user_args.keys() - default_args.keys()
if unknown_args:
    raise TypeError('Unknown arguments: {}'.format(unknown_args))

# Update args to contain user-supplied arguments
args.update(kwargs)

unknown_argsявляется setсодержащими имена аргументов , которые не встречаются в значениях по умолчанию.


0

Другое простое решение для обработки неизвестных или нескольких аргументов может быть:

class ExampleClass(object):

    def __init__(self, x, y, **kwargs):
      self.x = x
      self.y = y
      self.attributes = kwargs

    def SomeFunction(self):
      if 'something' in self.attributes:
        dosomething()

0

** kwargs дает свободу добавлять любое количество аргументов ключевого слова. У каждого может быть список ключей, для которых он может устанавливать значения по умолчанию. Но установка значений по умолчанию для неопределенного количества ключей кажется ненужной. Наконец, может быть важно иметь ключи в качестве атрибутов экземпляра. Итак, я бы сделал это следующим образом:

class Person(object):
listed_keys = ['name', 'age']

def __init__(self, **kwargs):
    _dict = {}
    # Set default values for listed keys
    for item in self.listed_keys: 
        _dict[item] = 'default'
    # Update the dictionary with all kwargs
    _dict.update(kwargs)

    # Have the keys of kwargs as instance attributes
    self.__dict__.update(_dict)
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.