Default arguments with *args and **kwargs

前端 未结 7 1249
刺人心
刺人心 2020-11-27 12:12

In Python 2.x (I use 2.7), which is the proper way to use default arguments with *args and **kwargs?
I\'ve found a question on

相关标签:
7条回答
  • 2020-11-27 12:41

    This answer is an extension of what Daniel Américo suggested.

    This decorator assigns default kwarg values if they are not strictly defined.

    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
    

    Result

    @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'}
    
    

    Variant

    If you want to use the default values as written in the function definition, you could access the argument default values using f.func_defaults, which lists the default values. You would have to zip them with the end of the f.__code__.varnames to match these default values with the variable names.

    0 讨论(0)
  • 2020-11-27 12:50

    Similar approach to @yaccob, but clear and concise:

    In Python 3.5 or greater:

    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}
    

    Note: you can use 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 讨论(0)
  • 2020-11-27 12:51

    Sticking quite close to your solution approach while trying to make it more generic and more compact I would suggest to consider something like this:

    >>> 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'})
    
    0 讨论(0)
  • 2020-11-27 12:54

    The syntax in the other question is python3.x only and specifies keyword only arguments. It doesn't work on python2.x.

    For python2.x, I would pop it out of kwargs:

    def func(arg1, arg2, *args, **kwargs):
        opt_arg = kwargs.pop('opt_arg', 'def_val')
    
    0 讨论(0)
  • 2020-11-27 13:01

    Another way to handle with 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)
    

    This handles passing arbitrary *args to the underlying call unlike @nneonneo's answer.

    0 讨论(0)
  • 2020-11-27 13:03

    Just put the default arguments before the *args:

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

    Now, b will be explicitly set if you pass it as a keyword argument or the second positional argument.

    Examples:

    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}
    

    Note that, in particular, foo(x, y, b=z) doesn't work because b is assigned by position in that case.


    This code works in Python 3 too. Putting the default arg after *args in Python 3 makes it a "keyword-only" argument that can only be specified by name, not by position. If you want a keyword-only argument in Python 2, you can use @mgilson's solution.

    0 讨论(0)
提交回复
热议问题