Gì **
(sao đôi) và *
(sao) làm cho các thông số
Chúng cho phép các hàm được xác định để chấp nhận và cho người dùng vượt qua bất kỳ số lượng đối số, vị trí ( *
) và từ khóa ( **
).
Hàm xác định
*args
cho phép bất kỳ số lượng đối số vị trí tùy chọn (tham số), sẽ được gán cho một bộ dữ liệu có tên args
.
**kwargs
cho phép bất kỳ số lượng đối số từ khóa tùy chọn (tham số), sẽ có trong một dict có tên kwargs
.
Bạn có thể (và nên) chọn bất kỳ tên thích hợp nào, nhưng nếu ý định dành cho các đối số là về ngữ nghĩa không cụ thể args
và kwargs
là tên chuẩn.
Mở rộng, Vượt qua bất kỳ số lượng đối số
Bạn cũng có thể sử dụng *args
và **kwargs
chuyển các tham số từ danh sách (hoặc bất kỳ lần lặp nào) và dicts (hoặc bất kỳ ánh xạ nào), tương ứng.
Hàm nhận các tham số không phải biết rằng chúng đang được mở rộng.
Ví dụ, xrange của Python 2 không mong đợi rõ ràng *args
, nhưng vì nó lấy 3 số nguyên làm đối số:
>>> x = xrange(3) # create our *args - an iterable of 3 integers
>>> xrange(*x) # expand here
xrange(0, 2, 2)
Một ví dụ khác, chúng ta có thể sử dụng mở rộng dict trong str.format
:
>>> foo = 'FOO'
>>> bar = 'BAR'
>>> 'this is foo, {foo} and bar, {bar}'.format(**locals())
'this is foo, FOO and bar, BAR'
Tính năng mới trong Python 3: Xác định hàm với các đối số chỉ từ khóa
Bạn có thể có các đối số chỉ từ khóa sau *args
- ví dụ: ở đây, kwarg2
phải được cung cấp dưới dạng đối số từ khóa - không phải theo vị trí:
def foo(arg, kwarg=None, *args, kwarg2=None, **kwargs):
return arg, kwarg, args, kwarg2, kwargs
Sử dụng:
>>> foo(1,2,3,4,5,kwarg2='kwarg2', bar='bar', baz='baz')
(1, 2, (3, 4, 5), 'kwarg2', {'bar': 'bar', 'baz': 'baz'})
Ngoài ra, *
có thể được sử dụng bởi chính nó để chỉ ra rằng các đối số chỉ từ khóa theo sau, mà không cho phép các đối số vị trí không giới hạn.
def foo(arg, kwarg=None, *, kwarg2=None, **kwargs):
return arg, kwarg, kwarg2, kwargs
Ở đây, kwarg2
một lần nữa phải là một đối số từ khóa được đặt tên rõ ràng:
>>> foo(1,2,kwarg2='kwarg2', foo='foo', bar='bar')
(1, 2, 'kwarg2', {'foo': 'foo', 'bar': 'bar'})
Và chúng tôi không còn có thể chấp nhận các đối số vị trí không giới hạn vì chúng tôi không có *args*
:
>>> foo(1,2,3,4,5, kwarg2='kwarg2', foo='foo', bar='bar')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() takes from 1 to 2 positional arguments
but 5 positional arguments (and 1 keyword-only argument) were given
Một lần nữa, đơn giản hơn, ở đây chúng tôi yêu cầu kwarg
được cung cấp theo tên, không phải theo vị trí:
def bar(*, kwarg=None):
return kwarg
Trong ví dụ này, chúng tôi thấy rằng nếu chúng tôi cố gắng vượt qua kwarg
vị trí, chúng tôi sẽ gặp lỗi:
>>> bar('kwarg')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: bar() takes 0 positional arguments but 1 was given
Chúng ta phải vượt qua kwarg
tham số một cách rõ ràng như một đối số từ khóa.
>>> bar(kwarg='kwarg')
'kwarg'
Bản demo tương thích Python 2
*args
(thường nói "star-args") và **kwargs
(các ngôi sao có thể được ngụ ý bằng cách nói "kwargs", nhưng phải rõ ràng với "kwargs sao đôi") là những thành ngữ phổ biến của Python khi sử dụng ký hiệu *
và **
ký hiệu. Những tên biến cụ thể này không bắt buộc (ví dụ: bạn có thể sử dụng *foos
và **bars
), nhưng việc rời khỏi quy ước có thể sẽ khiến các lập trình viên Python của bạn nổi giận.
Chúng ta thường sử dụng những thứ này khi chúng ta không biết chức năng của mình sẽ nhận được gì hoặc có bao nhiêu đối số chúng ta có thể vượt qua, và đôi khi ngay cả khi đặt tên riêng cho mỗi biến sẽ rất lộn xộn và dư thừa (nhưng đây là trường hợp thường rõ ràng tốt hơn là ngầm).
ví dụ 1
Hàm sau mô tả cách chúng có thể được sử dụng và thể hiện hành vi. Lưu ý b
đối số được đặt tên sẽ được sử dụng bởi đối số vị trí thứ hai trước:
def foo(a, b=10, *args, **kwargs):
'''
this function takes required argument a, not required keyword argument b
and any number of unknown positional arguments and keyword arguments after
'''
print('a is a required argument, and its value is {0}'.format(a))
print('b not required, its default value is 10, actual value: {0}'.format(b))
# we can inspect the unknown arguments we were passed:
# - args:
print('args is of type {0} and length {1}'.format(type(args), len(args)))
for arg in args:
print('unknown arg: {0}'.format(arg))
# - kwargs:
print('kwargs is of type {0} and length {1}'.format(type(kwargs),
len(kwargs)))
for kw, arg in kwargs.items():
print('unknown kwarg - kw: {0}, arg: {1}'.format(kw, arg))
# But we don't have to know anything about them
# to pass them to other functions.
print('Args or kwargs can be passed without knowing what they are.')
# max can take two or more positional args: max(a, b, c...)
print('e.g. max(a, b, *args) \n{0}'.format(
max(a, b, *args)))
kweg = 'dict({0})'.format( # named args same as unknown kwargs
', '.join('{k}={v}'.format(k=k, v=v)
for k, v in sorted(kwargs.items())))
print('e.g. dict(**kwargs) (same as {kweg}) returns: \n{0}'.format(
dict(**kwargs), kweg=kweg))
Chúng tôi có thể kiểm tra trợ giúp trực tuyến để biết chữ ký của hàm, với help(foo)
, cho chúng tôi biết
foo(a, b=10, *args, **kwargs)
Hãy gọi hàm này với foo(1, 2, 3, 4, e=5, f=6, g=7)
bản in nào:
a is a required argument, and its value is 1
b not required, its default value is 10, actual value: 2
args is of type <type 'tuple'> and length 2
unknown arg: 3
unknown arg: 4
kwargs is of type <type 'dict'> and length 3
unknown kwarg - kw: e, arg: 5
unknown kwarg - kw: g, arg: 7
unknown kwarg - kw: f, arg: 6
Args or kwargs can be passed without knowing what they are.
e.g. max(a, b, *args)
4
e.g. dict(**kwargs) (same as dict(e=5, f=6, g=7)) returns:
{'e': 5, 'g': 7, 'f': 6}
Ví dụ 2
Chúng tôi cũng có thể gọi nó bằng cách sử dụng chức năng khác, trong đó chúng tôi chỉ cung cấp a
:
def bar(a):
b, c, d, e, f = 2, 3, 4, 5, 6
# dumping every local variable into foo as a keyword argument
# by expanding the locals dict:
foo(**locals())
bar(100)
in:
a is a required argument, and its value is 100
b not required, its default value is 10, actual value: 2
args is of type <type 'tuple'> and length 0
kwargs is of type <type 'dict'> and length 4
unknown kwarg - kw: c, arg: 3
unknown kwarg - kw: e, arg: 5
unknown kwarg - kw: d, arg: 4
unknown kwarg - kw: f, arg: 6
Args or kwargs can be passed without knowing what they are.
e.g. max(a, b, *args)
100
e.g. dict(**kwargs) (same as dict(c=3, d=4, e=5, f=6)) returns:
{'c': 3, 'e': 5, 'd': 4, 'f': 6}
Ví dụ 3: sử dụng thực tế trong trang trí
OK, vì vậy có lẽ chúng ta chưa thấy tiện ích này. Vì vậy, hãy tưởng tượng bạn có một số chức năng với mã dự phòng trước và / hoặc sau mã phân biệt. Các hàm được đặt tên sau đây chỉ là mã giả cho mục đích minh họa.
def foo(a, b, c, d=0, e=100):
# imagine this is much more code than a simple function call
preprocess()
differentiating_process_foo(a,b,c,d,e)
# imagine this is much more code than a simple function call
postprocess()
def bar(a, b, c=None, d=0, e=100, f=None):
preprocess()
differentiating_process_bar(a,b,c,d,e,f)
postprocess()
def baz(a, b, c, d, e, f):
... and so on
Chúng tôi có thể xử lý vấn đề này một cách khác nhau, nhưng chúng tôi chắc chắn có thể trích xuất sự dư thừa bằng một công cụ trang trí, và vì vậy ví dụ dưới đây của chúng tôi cho thấy cách thức *args
và **kwargs
có thể rất hữu ích:
def decorator(function):
'''function to wrap other functions with a pre- and postprocess'''
@functools.wraps(function) # applies module, name, and docstring to wrapper
def wrapper(*args, **kwargs):
# again, imagine this is complicated, but we only write it once!
preprocess()
function(*args, **kwargs)
postprocess()
return wrapper
Và bây giờ mọi chức năng được bao bọc có thể được viết ngắn gọn hơn nhiều, vì chúng tôi đã phát hiện ra sự dư thừa:
@decorator
def foo(a, b, c, d=0, e=100):
differentiating_process_foo(a,b,c,d,e)
@decorator
def bar(a, b, c=None, d=0, e=100, f=None):
differentiating_process_bar(a,b,c,d,e,f)
@decorator
def baz(a, b, c=None, d=0, e=100, f=None, g=None):
differentiating_process_baz(a,b,c,d,e,f, g)
@decorator
def quux(a, b, c=None, d=0, e=100, f=None, g=None, h=None):
differentiating_process_quux(a,b,c,d,e,f,g,h)
Và bằng cách thanh toán ra mã của chúng tôi, trong đó *args
và **kwargs
cho phép chúng ta làm, chúng ta giảm dòng mã, cải thiện khả năng đọc và bảo trì, và có vị trí kinh điển duy nhất cho logic trong chương trình của chúng tôi. Nếu chúng ta cần thay đổi bất kỳ phần nào của cấu trúc này, chúng ta có một nơi để thực hiện mỗi thay đổi.