2009-09-12 23 views
51

Tôi đến từ một nền tảng trong ngôn ngữ tĩnh. Ai đó có thể giải thích (lý tưởng thông qua ví dụ) thế giới thực lợi thế của việc sử dụng ** kwargs trên các đối số được đặt tên?Tại sao sử dụng ** kwargs trong python? Một số lợi thế của thế giới thực khi sử dụng các đối số được đặt tên là gì?

Với tôi, nó dường như chỉ làm cho hàm gọi trở nên mơ hồ hơn. Cảm ơn.

+1

Hãy suy nghĩ về varargs trong C - đôi khi bạn không thể biết những gì lập luận của bạn sẽ được. –

+10

Vui lòng không sử dụng cụm từ "thế giới thực" - đó là tranh luận. Nó nói rằng "tất cả các ví dụ tôi đã nhìn thấy cho đến nay là contrived và vô ích; ứng dụng của tôi là thế giới thực, ví dụ của bạn là một thế giới tưởng tượng." Vui lòng thay đổi câu hỏi của bạn để loại bỏ "thế giới thực". –

+67

Chỉ có người thích tranh luận mới nghĩ rằng câu hỏi của tôi là tranh luận. – meppum

Trả lời

32

ví dụ thực thế giới:

trang trí - chúng thường chung chung, vì vậy bạn không thể xác định các đối số trả trước:

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

Nơi bạn muốn làm phép thuật với số lượng đối số từ khóa không xác định. ORM Django nào đó, ví dụ:

Model.objects.filter(foo__lt = 4, bar__iexact = 'bar') 
4

**kwargs là tốt nếu bạn không biết trước tên của các tham số. Ví dụ: hàm tạo dict sử dụng chúng để khởi tạo các khóa của từ điển mới.

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} 
+1

Nó thường được sử dụng nếu bạn muốn chuyển nhiều đối số đến một hàm khác mà bạn không nhất thiết phải biết các tùy chọn.Ví dụ, specialplot (a, ** kwargs) có thể chuyển các tùy chọn lô trong kwargs đến một hàm lô chung mà chấp nhận các tùy chọn đó dưới dạng tham số đã đặt tên. – Tristan

+1

Mặc dù tôi cho rằng kiểu xấu, vì nó cản trở nội tâm. – bayer

0

Một ví dụ đang triển khai python-argument-binders, được sử dụng như thế này:

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

Đây là từ functools.partial docs python: partial là 'tương đối tương đương' để impl này:

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

ở đây, vì 'từ khoá' là từ điển, không phải .copy() chỉ cần tạo một con trỏ khác? có nghĩa là 'fkeywords' đang được thêm vào từ điển gốc. tôi nghĩ bạn muốn sử dụng copy.deepcopy() để tạo một bản sao thực sự của từ điển, phải không? – aeroNotAuto

+0

* 'currying' * là tên đường phố cho * 'đối số một phần ràng buộc' * – smci

51

Bạn có thể muốn chấp nhận các đối số được đặt tên gần như tùy ý vì một loạt các lý do - và đó là biểu mẫu **kw cho phép bạn thực hiện.

Lý do phổ biến nhất là chuyển đối số ngay cho một số chức năng khác mà bạn đang gói (trang trí là một trường hợp này, nhưng FAR từ chỉ có duy nhất!) - trong trường hợp này, **kw sẽ mất liên kết giữa wrapper và wrappee, vì trình bao bọc không phải biết hoặc quan tâm đến tất cả các đối số của trình bao bọc. Đây là một lý do hoàn toàn khác:

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

nếu tất cả các tên phải được biết trước, thì rõ ràng cách tiếp cận này không thể tồn tại, phải không? Và btw, khi áp dụng, tôi rất thích cách này thực hiện một dict có phím chuỗi chữ to:

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

đơn giản chỉ vì sau này là khá dấu chấm câu nặng và do đó ít có thể đọc được.

Khi không có lý do tuyệt vời nào để chấp nhận **kwargs áp dụng, thì đừng chấp nhận: đơn giản như vậy. IOW, nếu không có lý do chính đáng để cho phép người gọi chuyển thêm tên args với tên tùy ý, đừng cho phép điều đó xảy ra - chỉ cần tránh đặt một biểu mẫu **kw ở cuối chữ ký của hàm trong câu lệnh def.

Đối với sử dụng**kw trong một cuộc gọi, cho phép bạn đặt cùng các thiết lập chính xác các đối số tên mà bạn phải vượt qua, đều có giá trị tương ứng, trong một dict, độc lập với một điểm gọi duy nhất, sau đó sử dụng dict rằng tại điểm gọi duy nhất. Hãy so sánh:

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

tới:

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

Thậm chí chỉ với hai khả năng (! Và thuộc loại rất đơn giản), thiếu **kw được aleady làm tùy chọn thứ hai hoàn toàn không có cơ sở và không thể chấp nhận - chỉ hãy tưởng tượng cách nó phát ra khi có nửa tá khả năng, có thể trong tương tác phong phú hơn một chút ... mà không có **kw, cuộc sống sẽ là địa ngục tuyệt đối trong những hoàn cảnh như vậy!

+0

+1 - đáng ngạc nhiên hữu ích để có thể làm điều này nhưng một cái gì đó của một sự thay đổi mô hình nếu bạn đang sử dụng để (nói) java. – ConcernedOfTunbridgeWells

10

Có hai trường hợp phổ biến:

Đầu tiên: Bạn đang quấn một chức năng mà phải mất một số tranh cãi từ khóa, nhưng bạn chỉ cần đi để vượt qua chúng cùng:

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

Thứ hai: Bạn là sẵn sàng chấp nhận bất kỳ tranh cãi từ khóa, ví dụ, để thiết lập các thuộc tính trên một đối tượng:

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

một lý do bạn có thể muốn sử dụng **kwargs (và 0.123.) là nếu bạn đang mở rộng một phương thức hiện có trong một lớp con. Bạn muốn vượt qua tất cả các đối số hiện vào phương pháp của lớp cha, nhưng muốn đảm bảo rằng lớp học của bạn duy trì hoạt động ngay cả khi thay đổi chữ ký trong một phiên bản tương lai:

class MySubclass(Superclass): 
    def __init__(self, *args, **kwargs): 
     self.myvalue = kwargs.pop('myvalue', None) 
     super(MySubclass, self).__init__(*args, **kwargs) 
+4

+1: Thực hiện điều này rất, rất thường xuyên. –

2

Dưới đây là một ví dụ, tôi sử dụng trong CGI Python. Tôi đã tạo một lớp mất **kwargs thành hàm __init__. Điều này cho phép tôi bắt chước DOM trên server-side với các lớp:

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

Vấn đề duy nhất là bạn không thể làm những điều sau đây, vì class là một từ khóa Python.

Div(class = 'foo') 

Giải pháp là truy cập từ điển cơ bản.

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

Tôi không nói rằng đây là cách sử dụng "chính xác" tính năng này. Những gì tôi đang nói là có tất cả các loại cách thức không phù hợp trong đó các tính năng như thế này có thể được sử dụng.

1

Và đây là một ví dụ điển hình:

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

def proclaim(object_, message, data): 
    print(MESSAGE.format(**locals())) 
Các vấn đề liên quan