Зачем использовать ** kwargs в Python? Каковы некоторые реальные преимущества перед использованием именованных аргументов? - PullRequest
61 голосов
/ 12 сентября 2009

Я пришел из прошлого на статических языках. Может ли кто-нибудь объяснить (в идеале на примере) реальный мир преимуществ использования ** kwargs над именованными аргументами ?

Мне кажется, это только делает вызов функции более двусмысленным. Спасибо.

Ответы [ 8 ]

58 голосов
/ 12 сентября 2009

Возможно, вы захотите принять почти произвольные именованные аргументы по ряду причин - и это то, что позволяет вам сделать форма **kw.

Самая распространенная причина заключается в том, чтобы передать аргументы прямо в какую-то другую функцию, которую вы переносите (декораторы - один из случаев, но FAR - единственный!) - в этом случае **kw ослабляет связь между оберткой и wrappee, поскольку обертка не должна знать или заботиться обо всех аргументах wrappee. Вот еще одна, совершенно другая причина:

d = dict(a=1, b=2, c=3, d=4)

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

d = {'a': 1, 'b': 2, 'c': 3, 'd': 4}

просто потому, что последний довольно пунктуален и, следовательно, менее читабелен.

Если ни одна из веских причин для принятия **kwargs не применима, не принимайте ее: это так просто. Итак, если нет веской причины разрешать вызывающей стороне передавать дополнительные именованные аргументы с произвольными именами, не допускайте этого - просто избегайте помещать форму **kw в конце подписи функции в операторе def .

Что касается , использующего **kw в вызове, который позволяет вам собрать точный набор именованных аргументов, которые вы должны передать, каждый с соответствующими значениями, в dict, независимо от одной точки вызова , затем используйте этот dict в одной точке вызова. Сравните:

if x: kw['x'] = x
if y: kw['y'] = y
f(**kw)

до:

if x:
  if y:
    f(x=x, y=y)
  else:
    f(x=x)
else:
  if y:
    f(y=y)
  else:
    f()

Даже с двумя возможностями (и очень простыми!) Отсутствие **kw позволяет сделать второй вариант абсолютно несостоятельным и невыносимым - просто представьте, как он работает, когда существует полдюжины возможностей, возможно, в немного более насыщенном взаимодействии ... без **kw жизнь была бы абсолютным адом при таких обстоятельствах!

39 голосов
/ 12 сентября 2009

Другая причина, по которой вы можете захотеть использовать **kwargs*args), - это расширение существующего метода в подклассе. Вы хотите передать все существующие аргументы в метод суперкласса, но хотите убедиться, что ваш класс продолжает работать, даже если сигнатура изменится в следующей версии:

class MySubclass(Superclass):
    def __init__(self, *args, **kwargs):
        self.myvalue = kwargs.pop('myvalue', None)
        super(MySubclass, self).__init__(*args, **kwargs)
37 голосов
/ 12 сентября 2009

Примеры из реальной жизни:

Декораторы - они обычно являются общими, поэтому вы не можете указать аргументы заранее:

def decorator(old):
    def new(*args, **kwargs):
        # ...
        return old(*args, **kwargs)
    return new

Места, где вы хотите творить чудеса с неизвестным числом ключевых слов. ORM Джанго делает это, например ::1006

Model.objects.filter(foo__lt = 4, bar__iexact = 'bar')
11 голосов
/ 12 сентября 2009

Есть два распространенных случая:

Во-первых: вы оборачиваете другую функцию, которая принимает несколько аргументов ключевого слова, но вы просто собираетесь передать их:

def my_wrapper(a, b, **kwargs):
    do_something_first(a, b)
    the_real_function(**kwargs)

Второе: вы готовы принять любой аргумент ключевого слова, например, чтобы установить атрибуты для объекта:

class OpenEndedObject:
    def __init__(self, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)

foo = OpenEndedObject(a=1, foo='bar')
assert foo.a == 1
assert foo.foo == 'bar'
4 голосов
/ 12 сентября 2009

**kwargs хорошо, если вы заранее не знаете названия параметров. Например, конструктор dict использует их для инициализации ключей нового словаря.

dict(**kwargs) -> new dictionary initialized with the name=value pairs
    in the keyword argument list.  For example:  dict(one=1, two=2)
In [3]: dict(one=1, two=2)
Out[3]: {'one': 1, 'two': 2}
3 голосов
/ 12 сентября 2009

Вот пример, который я использовал в CGI Python. Я создал класс, который взял **kwargs для функции __init__. Это позволило мне эмулировать DOM на стороне сервера с помощью классов:

document = Document()
document.add_stylesheet('style.css')
document.append(Div(H1('Imagist\'s Page Title'), id = 'header'))
document.append(Div(id='body'))

Единственная проблема в том, что вы не можете сделать следующее, потому что class является ключевым словом Python.

Div(class = 'foo')

Решение состоит в том, чтобы получить доступ к основному словарю.

Div(**{'class':'foo'})

Я не говорю, что это «правильное» использование этой функции. Я говорю о том, что существует множество непредвиденных способов использования подобных функций.

1 голос
/ 13 сентября 2009

А вот еще один типичный пример:

MESSAGE = "Lo and behold! A message {message!r} came from {object_} with data {data!r}."

def proclaim(object_, message, data):
    print(MESSAGE.format(**locals()))
0 голосов
/ 12 сентября 2009

Один из примеров - реализация python-arguments-binders , используемая следующим образом:

>>> from functools import partial
>>> def f(a, b):
...     return a+b
>>> p = partial(f, 1, 2)
>>> p()
3
>>> p2 = partial(f, 1)
>>> p2(7)
8

Это из functools.partial документов на python: частичное «относительно эквивалентно» этому значению:

def partial(func, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = keywords.copy()
        newkeywords.update(fkeywords)
        return func(*(args + fargs), **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc
...