Đối số mặc định với * args và ** kwargs


84

Trong Python 2.x (tôi sử dụng 2.7), cách thích hợp để sử dụng các đối số mặc định với *args**kwargs?
Tôi đã tìm thấy một câu hỏi trên SO liên quan đến chủ đề này, nhưng đó là Python 3 :
Gọi một hàm Python với * args, ** kwargs và các đối số tùy chọn / mặc định

Ở đó, họ nói rằng phương pháp này hoạt động:

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

Trong 2.7, kết quả là a SyntaxError. Có cách nào được khuyến nghị để xác định một chức năng như vậy không?
Tôi đã làm cho nó hoạt động theo cách này, nhưng tôi đoán có một giải pháp tốt hơn.

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

Vẫn là lời giải thích ngắn gọn nhất mà tôi đã xem qua: Saltcrane.com/blog/2008/01/…
verbsintransit Ngày

4
Không bao giờ gọi một cách rõ ràng __contains__. Luôn luôn sử dụng in: 'opt_arg' in kwargs. (Thậm chí tốt hơn: kwargs.get('opt_arg', 'def_val')như trong câu trả lời của mgilson).
nneonneo

Câu trả lời:


80

Chỉ cần đặt các đối số mặc định trước *args:

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

Hiện nay, b sẽ được đặt rõ ràng nếu bạn chuyển nó dưới dạng đối số từ khóa hoặc đối số vị trí thứ hai.

Ví dụ:

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}

Lưu ý rằng, đặc biệt, foo(x, y, b=z)không hoạt động vì bđược chỉ định theo vị trí trong trường hợp đó.


Mã này cũng hoạt động trong Python 3. Đặt đối số mặc định sau *args trong Python 3 làm cho nó trở thành đối số "chỉ từ khóa" chỉ có thể được chỉ định theo tên, không phải theo vị trí. Nếu bạn muốn một đối số chỉ từ khóa trong Python 2, bạn có thể sử dụng giải pháp của @ mgilson.


1
Vâng, đó là do sự mơ hồ của *args. Cách của bạn là đúng nếu bạn muốn một đối số chỉ có từ khóa.
nneonneo

Chỉ cần nhận được câu trả lời trong các ý kiến ​​ở trên. (Điều gì sẽ xảy ra nếu tôi muốn gọi hàm với một b khác và tôi cũng muốn thêm * args?)

Dù sao, trước khi hỏi, tôi đã thử giải pháp này, nhưng tôi cũng thấy nó chỉ hoạt động nếu sau khi xác định opt_arg, tôi chỉ sử dụng kwargs.

@nneonneo: Tôi đã lấy các ví dụ của bạn, nhưng cách này vẫn không cho chúng tôi quyền tự do chỉ định các đối số mặc định và * args cùng một lúc, như Python 3.x cho phép điều đó, như được giải thích trong liên kết; Phải không?

5
Bạn không thể để một đối số mặc định trong khi điền vào *args, không. Đây là lý do tại sao các chức năng đã được thêm vào Python 3. Thông thường, đối số mặc định trong Python 2 được quy định như một cái gì đó hiển nhiên như 0 hoặc Noneđể họ có thể được thông qua một cách rõ ràng trong.
nneonneo

55

Cú pháp trong câu hỏi khác chỉ là python3.x và chỉ định các đối số của từ khóa. Nó không hoạt động trên python2.x.

Đối với python2.x, tôi muốn popnó ra khỏi kwargs:

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

Nó cũng chỉ định * args, trước đối số mặc định.

4

Bạn cũng có thể sử dụng một trình trang trí như thế này:

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

Sau đó, chỉ cần làm:

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

Ví dụ:

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

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

1

Bám sát khá gần với cách tiếp cận giải pháp của bạn trong khi cố gắng làm cho nó chung chung hơn và nhỏ gọn hơn, tôi khuyên bạn nên xem xét một số điều như sau:

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

Một cách khác để xử lý với 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)

Điều này xử lý việc chuyển tùy ý *argsđến cuộc gọi cơ bản không giống như câu trả lời của @ nneonneo.


1
Điều này sẽ được rõ ràng hơn nếu bạn sử dụng mã hợp lệ, tức là 'opt_arg'thay vì < kwarg-name >'def_val'thay vì< kwarg-name-default-value >
wjandrea

0

Câu trả lời này là một phần mở rộng của những gì Daniel Américo đề xuất .

Trình trang trí này chỉ định các giá trị kwarg mặc định nếu chúng không được xác định chặt chẽ .

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

Kết quả

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

Biến thể

Nếu bạn muốn sử dụng các giá trị mặc định như được viết trong định nghĩa hàm, bạn có thể truy cập các giá trị mặc định của đối số bằng cách sử dụng f.func_defaults, trong đó liệt kê các giá trị mặc định. Bạn sẽ phải đặt zipchúng bằng đuôi của f.__code__.varnamesđể khớp các giá trị mặc định này với tên biến.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.