Dlaczego warto używać * * kwargs w Pythonie? Jakie są realne zalety używania nazwanych argumentów?

Pochodzę z tła w językach statycznych. Czy ktoś może wyjaśnić (najlepiej na przykładzie) zalety używania **kwargs nad nazwanymi argumentami ?

Dla mnie to tylko sprawia, że wywołanie funkcji jest bardziej niejednoznaczne. Dzięki.

Author: binary lobster, 2009-09-12

8 answers

Prawdziwe przykłady:

Dekoratory-są zazwyczaj ogólne, więc nie można podać argumentów z góry:

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

Miejsca, w których chcesz zrobić magię z nieznaną liczbą argumentów słów kluczowych. ORM Django tak robi, np.:

Model.objects.filter(foo__lt = 4, bar__iexact = 'bar')
 36
Author: Cat Plus Plus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-12 18:50:27

Możesz chcieć zaakceptować niemal dowolne argumenty nazwane z wielu powodów - i na to pozwala formularz **kw.

Najczęstszym powodem jest przekazywanie argumentów bezpośrednio do innej funkcji, którą zawijasz (dekoratory są jednym z takich przypadków, ale nie jedynym!) -- w tym przypadku **kw rozluźnia połączenie pomiędzy wrapperem a wrappee, ponieważ wrapper nie musi znać ani przejmować się wszystkimi argumentami wrappee. Oto kolejny, zupełnie inny powód:

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

Jeśli wszystkie nazwiska muszą być znane z góry, to oczywiście takie podejście nie mogłoby istnieć, prawda? A btw, jeśli ma to zastosowanie, zdecydowanie wolę ten sposób robienia dict, którego klucze są literalnymi ciągami do:

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

Po prostu dlatego, że ten ostatni jest dość ciężki w interpunkcji, a przez to mniej czytelny.

Jeśli nie ma zastosowania żaden z doskonałych powodów do zaakceptowania **kwargs, nie Akceptuj tego: to takie proste. IOW, jeśli nie ma dobrego powodu, aby pozwolić na aby przekazać dodatkowe args o dowolnych nazwach, nie pozwól na to-unikaj umieszczania formularza **kw na końcu podpisu funkcji w instrukcji def.

Jak dla za pomocą **kw w wywołaniu, które pozwala połączyć dokładny zestaw nazwanych argumentów, które musisz przekazać, każdy z odpowiednimi wartościami, w dict, niezależnie od pojedynczego punktu wywoławczego, a następnie użyć tego dict w pojedynczym punkcie wywoławczym. Porównaj:

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

Do:

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

Nawet z zaledwie dwoma możliwościami (i bardzo najprostszego rodzaju!), brak **kw sprawia, że druga opcja jest absolutnie nie do utrzymania i nie do zniesienia-wyobraź sobie, jak to się rozgrywa, gdy jest pół tuzina możliwości, być może w nieco bogatszej interakcji... Bez **kw, życie byłoby absolutnym piekłem w takich okolicznościach!

 57
Author: Alex Martelli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-03 16:53:59

Innym powodem, dla którego warto użyć **kwargs (i *args) jest rozszerzenie istniejącej metody w podklasie. Chcesz przekazać wszystkie istniejące argumenty do metody superclass, ale chcesz mieć pewność, że twoja klasa nadal będzie działać, nawet jeśli podpis ulegnie zmianie w przyszłej wersji:

class MySubclass(Superclass):
    def __init__(self, *args, **kwargs):
        self.myvalue = kwargs.pop('myvalue', None)
        super(MySubclass, self).__init__(*args, **kwargs)
 36
Author: Daniel Roseman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-12 19:15:33

Istnieją dwa typowe przypadki:

Po pierwsze: owijasz inną funkcję, która pobiera kilka argumentów słów kluczowych, ale po prostu je przekazujesz:

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

Po drugie: możesz zaakceptować dowolny argument ze słowem kluczowym, na przykład, aby ustawić atrybuty na obiekcie:

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'
 10
Author: Ned Batchelder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-12 18:56:22

**kwargs są dobre, jeśli nie znasz z góry nazwy parametrów. Na przykład konstruktor dict używa ich do inicjalizacji kluczy nowego słownika.

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}
 4
Author: Cristian Ciupitu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-12 18:45:42

Oto przykład, którego użyłem w CGI Python. Stworzyłem klasę, która wzięła **kwargs do funkcji __init__. To pozwoliło mi emulować DOM po stronie serwera z klasami:

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

Jedynym problemem jest to, że nie możesz wykonać następujących czynności, ponieważ class jest słowem kluczowym Pythona.

Div(class = 'foo')

Rozwiązaniem jest dostęp do podstawowego słownika.

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

Nie twierdzę, że jest to "poprawne" użycie tej funkcji. Chodzi mi o to, że są różnego rodzaju niewymuszone sposoby w które funkcje tego typu mogą być używane.

 2
Author: Imagist,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-12 21:56:05

A oto kolejny typowy przykład:

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

def proclaim(object_, message, data):
    print(MESSAGE.format(**locals()))
 1
Author: ilya n.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-13 05:30:16

Jednym z przykładów jest implementacja Python-argument-binders , użyta Tak:

>>> 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

To jest z functools.partial python docs: partial jest 'względnie równoważny' do tego 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
 0
Author: Dustin Getz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:46:52