Я исхожу из фона на статических языках. Может ли кто-нибудь объяснить (в идеале, на примере) реальный мир преимущества использования ** kwargs над именованными аргументами?
Мне кажется, что вызов функции более неоднозначен. Спасибо.
Я исхожу из фона на статических языках. Может ли кто-нибудь объяснить (в идеале, на примере) реальный мир преимущества использования ** kwargs над именованными аргументами?
Мне кажется, что вызов функции более неоднозначен. Спасибо.
Примеры в реальном мире:
Декораторы - они обычно общие, поэтому вы не можете указать аргументы upfront:
def decorator(old):
def new(*args, **kwargs):
# ...
return old(*args, **kwargs)
return new
Места, где вы хотите сделать магию с неизвестным количеством аргументов ключевого слова. Django ORM делает это, например:
Model.objects.filter(foo__lt = 4, bar__iexact = 'bar')
Возможно, вы захотите принять практически произвольные именованные аргументы по ряду причин - и то, что позволяет форма **kw
.
Наиболее распространенная причина заключается в том, чтобы передать аргументы прямо на какую-то другую функцию, которую вы обертываете (декораторы - это один случай этого, но FAR от единственного!) - в этом случае **kw
ослабляет связь между оберткой и оберткой, поскольку обертка не должна знать или заботиться обо всех аргументах обертки. Здесь другая, совершенно другая причина:
d = dict(a=1, b=2, c=3, d=4)
если все имена должны были быть известны заранее, тогда, очевидно, такой подход просто не мог существовать, не так ли? И btw, когда это применимо, я предпочитаю этот способ сделать dict, ключи которого являются буквальными строками:
d = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
просто потому, что последний является довольно пунктуационным, и, следовательно, менее читаемым.
Если ни одна из отличных причин для принятия **kwargs
не применяется, тогда не принимайте ее: это так просто. IOW, если нет веских причин разрешить вызывающему абоненту передавать дополнительные имена args с произвольными именами, не позволяйте этому произойти - просто не помещайте форму **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
является aleady, делая второй вариант абсолютно несостоятельным и невыносимым - представьте себе, как это происходит, когда есть полдюжины возможностей, возможно в немного более богатом взаимодействии... без **kw
, жизнь была бы абсолютным адом при таких обстоятельствах!
Еще одна причина, по которой вы можете использовать **kwargs
(и *args
), - это расширение существующего метода в подклассе. Вы хотите передать все существующие аргументы в метод суперкласса, но хотите, чтобы ваш класс продолжал работать, даже если подпись изменилась в будущей версии:
class MySubclass(Superclass):
def __init__(self, *args, **kwargs):
self.myvalue = kwargs.pop('myvalue', None)
super(MySubclass, self).__init__(*args, **kwargs)
Существует два распространенных случая:
Сначала: вы обертываете другую функцию, которая принимает несколько аргументов ключевого слова, но вы просто передадите их:
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'
**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}
Вот пример, который я использовал в CGI Python. Я создал класс, который взял **kwargs
в функцию __init__
. Это позволило мне эмулировать DOM на стороне сервера с помощью классов:
document = Document()
document.add_stylesheet('style.css')
document.append(Div(H1('Imagist\ Page Title'), id = 'header'))
document.append(Div(id='body'))
Единственная проблема заключается в том, что вы не можете сделать следующее, потому что class
- это ключевое слово Python.
Div(class = 'foo')
Решение заключается в доступе к базовому словарю.
Div(**{'class':'foo'})
Я не говорю, что это "правильное" использование этой функции. Я говорю, что есть все виды непредвиденных способов, в которых можно использовать такие функции.
И вот еще один типичный пример:
MESSAGE = "Lo and behold! A message {message!r} came from {object_} with data {data!r}."
def proclaim(object_, message, data):
print(MESSAGE.format(**locals()))
Одним из примеров является python-argument-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 docs: partial является "относительно эквивалентным" этому impl:
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