Argomenti predefiniti con * args e ** kwargs


87

In Python 2.x (io uso 2.7), qual è il modo corretto di utilizzare gli argomenti predefiniti con *argse **kwargs?
Ho trovato una domanda su SO relativa a questo argomento, ma è per Python 3 :
Chiamare una funzione Python con * args, ** kwargs e argomenti opzionali / predefiniti

Lì, dicono che questo metodo funziona:

def func(arg1, arg2, *args, opt_arg='def_val', **kwargs):
    #...

In 2.7, risulta in a SyntaxError. Esiste un modo consigliato per definire una tale funzione?
Ho funzionato in questo modo, ma immagino che ci sia una soluzione migliore.

def func(arg1, arg2, *args, **kwargs):
    opt_arg ='def_val'
    if kwargs.__contains__('opt_arg'):
        opt_arg = kwargs['opt_arg']
    #...

Ancora la spiegazione più succinta che ho incontrato: saltycrane.com/blog/2008/01/…
verbsintransit

4
Non chiamare mai esplicitamente __contains__. Usare sempre in: 'opt_arg' in kwargs. (Ancora meglio: kwargs.get('opt_arg', 'def_val')come nella risposta di mgilson).
nneonneo

Risposte:


82

Basta mettere gli argomenti predefiniti prima di *args:

def foo(a, b=3, *args, **kwargs):

Ora, bverrà impostato in modo esplicito se lo passi come argomento della parola chiave o come secondo argomento posizionale.

Esempi:

foo(x) # a=x, b=3, args=(), kwargs={}
foo(x, y) # a=x, b=y, args=(), kwargs={}
foo(x, b=y) # a=x, b=y, args=(), kwargs={}
foo(x, y, z, k) # a=x, b=y, args=(z, k), kwargs={}
foo(x, c=y, d=k) # a=x, b=3, args=(), kwargs={'c': y, 'd': k}
foo(x, c=y, b=z, d=k) # a=x, b=z, args=(), kwargs={'c': y, 'd': k}

Nota che, in particolare, foo(x, y, b=z)non funziona perché bin quel caso viene assegnato dalla posizione.


Questo codice funziona anche in Python 3. Mettere l' argomento predefinito dopo *args in Python 3 lo rende un argomento "solo parola chiave" che può essere specificato solo per nome, non per posizione. Se vuoi un argomento di sola parola chiave in Python 2, puoi usare la soluzione di @ mgilson.


1
Sì, ciò è dovuto all'ambiguità di *args. Il tuo modo è corretto se desideri un argomento di sola parola chiave.
nneonneo

Ho appena ricevuto una risposta nei commenti sopra. (Cosa succede se voglio chiamare la funzione con un b diverso e voglio anche aggiungere * args?)

Ad ogni modo, prima di chiedere, ho provato questa soluzione, ma ho anche scoperto che funzionava solo se dopo aver definito opt_arg utilizzo solo kwargs.

@nneonneo: ho ricevuto i tuoi esempi, ma in questo modo ancora non ci dà la libertà di specificare argomenti predefiniti e * arg allo stesso tempo, come Python 3.x lo consente, come spiegato nel link; lo fa?

5
Non puoi lasciare un argomento predefinito da solo mentre compili *args, no. Questo è il motivo per cui la funzionalità è stata aggiunta a Python 3. In genere, gli argomenti predefiniti in Python 2 sono specificati come qualcosa di ovvio come 0 o in Nonemodo che possano essere passati esplicitamente.
nneonneo

56

La sintassi nell'altra domanda è solo python3.x e specifica solo argomenti di parole chiave. Non funziona su python2.x.

Per python2.x, lo farei popda kwargs:

def func(arg1, arg2, *args, **kwargs):
    opt_arg = kwargs.pop('opt_arg', 'def_val')

Specifica anche * args, prima dell'argomento predefinito.

4

Potresti anche usare un decoratore come questo:

import functools
def default_kwargs(**defaultKwargs):
    def actual_decorator(fn):
        @functools.wraps(fn)
        def g(*args, **kwargs):
            defaultKwargs.update(kwargs)
            return fn(*args, **defaultKwargs)
        return g
    return actual_decorator

Quindi fai semplicemente:

@default_kwargs(defaultVar1 = defaultValue 1, ...)
def foo(*args, **kwargs):
    # Anything in here

Per esempio:

@default_kwargs(a=1)
def f(*args, **kwargs):
    print(kwargs['a']+ 1)

f() # Returns 2
f(3) # Returns 4

1

Rimanendo abbastanza vicino al tuo approccio alla soluzione mentre cerchi di renderlo più generico e più compatto, suggerirei di considerare qualcosa del genere:

>>> def func(arg1, arg2, *args, **kwargs):
...     kwargs_with_defaults = dict({'opt_arg': 'def_val', 'opt_arg2': 'default2'}, **kwargs)
...     #...
...     return arg1, arg2, args, kwargs_with_defaults

>>> func('a1', 'a2', 'a3', 'a5', x='foo', y='bar')
('a1', 'a2', ('a3', 'a5'), {'opt_arg2': 'default2', 'opt_arg': 'def_val', 'y': 'bar', 'x': 'foo'})

>>> func('a1', 'a2', 'a3', 'a5', opt_arg='explicit_value', x='foo', y='bar')
('a1', 'a2', ('a3', 'a5'), {'opt_arg2': 'default2', 'opt_arg': 'explicit_value', 'y': 'bar', 'x': 'foo'})

1

Approccio simile a @yaccob, ma chiaro e conciso:

In Python 3.5 o versioni successive:

def foo(a, b=3, *args, **kwargs):
  defaultKwargs = { 'c': 10, 'd': 12 }
  kwargs = { **defaultKwargs, **kwargs }
  print(a, b, args, kwargs)
  
  # Do something    

foo(1) # 1 3 () {'c': 10, 'd': 12}
foo(1, d=5) # 1 3 () {'c': 10, 'd': 5}
foo(1, 2, 4, d=5) # 1 2 (4,) {'c': 10, 'd': 5}

Nota: puoi usare In Python 2

kwargs = merge_two_dicts(defaultKwargs, kwargs)

In Python 3.5

kwargs = { **defaultKwargs, **kwargs }

In Python 3.9

kwargs = defaultKwargs | kwargs  # NOTE: 3.9+ ONLY

0

Un altro modo per gestire con Python 2.x:

def foo(*args, **kwargs):
    if 'kwarg-name' not in kwargs.keys():
        kwargs['kwarg-name'] = 'kwarg-name-default-value'
    return bar(*args, **kwargs)

Questo gestisce il passaggio arbitrario *argsalla chiamata sottostante a differenza della risposta di @nneonneo.


1
Questo sarebbe più chiaro se 'opt_arg'< kwarg-name >'def_val'< kwarg-name-default-value >
usassi un

0

Questa risposta è un'estensione di quanto suggerito da Daniel Américo .

Questo decoratore assegna valori kwarg predefiniti se non sono definiti rigorosamente .

from functools import wraps

def force_kwargs(**defaultKwargs):
    def decorator(f):
        @wraps(f)
        def g(*args, **kwargs):
            new_args = {}
            new_kwargs = defaultKwargs
            varnames = f.__code__.co_varnames
            new_kwargs.update(kwargs)
            for k, v in defaultKwargs.items():
                if k in varnames:
                    i = varnames.index(k)
                    new_args[(i, k)] = new_kwargs.pop(k)
            # Insert new_args into the correct position of the args.
            full_args = list(args)
            for i, k in sorted(new_args.keys()):
                if i <= len(full_args):
                    full_args.insert(i, new_args.pop((i, k)))
                else:
                    break
            # re-insert the value as a key-value pair
            for (i, k), val in new_args.items():
                new_kwargs[k] = val
            return f(*tuple(full_args), **new_kwargs)
        return g
    return decorator

Risultato

@force_kwargs(c=7)
def f(a, b='B', c='C', d='D', *args, **kw):
    return a, b, c, d, args, kw
#                               a    b  c    d  args      kwargs
f('r')                      # 'r', 'B', 7, 'D',   (),         {}
f(1,2,3)                    #   1,   2, 7,   3,   (),         {}
f(1, 2, 3, b=3, c=9, f='F') #   1,   3, 9,   2, (3,), {'f': 'F'}

Variante

Se si desidera utilizzare i valori predefiniti come scritti nella definizione della funzione, è possibile accedere ai valori predefiniti dell'argomento utilizzando f.func_defaults, che elenca i valori predefiniti. Dovresti farlo zipcon la fine del f.__code__.varnamesper abbinare questi valori predefiniti con i nomi delle variabili.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.