Правильний спосіб використання ** 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 дає вам додаткову свободу не використовувати імена - тоді, однак, вам доведеться дотримуватися порядку.
balpha

13
@Kekoa: Ви завжди можете подавати названі аргументи в будь-якому обраному вами порядку. Вам не потрібно використовувати ** kwargs, щоб отримати цю гнучкість.
S.Lott

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
balpha

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
@Alan H .: якщо після того, як все вискакує, у кирги залишилось щось, то у вас є хибні значення.
Vinay Sajip

@VinaySajip: Гаразд, це чудовий момент у .pop "vs" .get, але я все ще не бачу, чому pop є кращим над названими аргументами, окрім того, що змушує абонента не використовувати позиційні параметри.
MestreLion

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

Отже, просто перевірка. Чи поп повертає значення словника, якщо ключ існує, а якщо ні, то він повертає default_valueпередане? І видаляє цей ключ згодом?
Даніель Мьоллер

43

Використовувати ** 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 or 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])

так, це елегантно і потужно ... не надто впевнений у квадратних дужках навколо допустимого_кейс_лістера: хоч я би зробив цей кортеж чи список, а потім відкину ці дужки у заяві "якщо"
Майк гризун

Я злегка модифікований це для випадків , коли всі ключі , як очікується: 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)

Багато використовується в ЦБС Джанго (наприклад 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 на основі певної логіки. Але це для підручника з Джанго.
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.
ettanany

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.