Cách sử dụng đúng ** kwargs trong Python


454

Cách thích hợp để sử dụng **kwargstrong Python khi nói đến các giá trị mặc định là gì?

kwargstrả về một từ điển, nhưng cách tốt nhất để đặt các giá trị mặc định là gì? Tôi có nên truy cập nó như một từ điển? Sử dụng chức năng get?

class ExampleClass:
    def __init__(self, **kwargs):
        self.val = kwargs['val']
        self.val2 = kwargs.get('val2')

Một câu hỏi đơn giản, nhưng một câu hỏi mà tôi không thể tìm thấy tài nguyên tốt. Mọi người thực hiện theo nhiều cách khác nhau trong mã mà tôi đã thấy và thật khó để biết nên sử dụng cái gì.

Câu trả lời:


468

Bạn có thể chuyển một giá trị mặc định get()cho các khóa không có trong từ điển:

self.val2 = kwargs.get('val2',"default value")

Tuy nhiên, nếu bạn có kế hoạch sử dụng một đối số cụ thể với một giá trị mặc định cụ thể, tại sao không sử dụng các đối số được đặt tên ở vị trí đầu tiên?

def __init__(self, val2="default value", **kwargs):

16
Tôi chỉ thích sử dụng các đối số vị trí cho các đối số được yêu cầu và kwargs cho các đối số có thể hoặc không thể được chỉ định, nhưng thật hữu ích khi có giá trị mặc định. kwargs là tốt vì bạn có thể gửi args của bạn theo bất kỳ thứ tự nào bạn chọn. Đối số vị trí không cung cấp cho bạn sự tự do đó.
Kekoa

95
Bạn có thể chuyển các đối số được đặt tên theo bất kỳ thứ tự nào bạn muốn. Bạn chỉ cần tuân thủ các vị trí nếu bạn không sử dụng tên - trong trường hợp kwargs, bạn phải làm. Thay vào đó, việc sử dụng các đối số được đặt tên trái ngược với kwargs cho bạn thêm tự do không sử dụng tên - tuy nhiên, sau đó, bạn phải giữ trật tự.
balpha

13
@Kekoa: Bạn luôn có thể gửi các đối số được đặt tên theo bất kỳ thứ tự nào bạn chọn. Bạn không cần phải sử dụng ** kwargs để có được sự linh hoạt này.
S.Lott

13
pylint cờ nó là hình thức xấu để sử dụng kwargs trong __init__(). Ai đó có thể giải thích tại sao đây là một sự vi phạm đáng giá?
hughdbrown


271

Trong khi hầu hết các câu trả lời đang nói rằng, ví dụ,

def f(**kwargs):
    foo = kwargs.pop('foo')
    bar = kwargs.pop('bar')
    ...etc...

là "giống như"

def f(foo=None, bar=None, **kwargs):
    ...etc...

Đây không phải là sự thật. Trong trường hợp sau, fcó thể được gọi là f(23, 42), trong khi các trường hợp trước đây chấp nhận tên đối số duy nhất - không có cuộc gọi vị trí. Thường thì bạn muốn cho phép người gọi linh hoạt tối đa và do đó, hình thức thứ hai, như hầu hết các câu trả lời khẳng định, là thích hợp hơn: nhưng điều đó không phải lúc nào cũng đúng. Khi bạn chấp nhận nhiều tham số tùy chọn trong đó thường chỉ có một vài thông số được thông qua, đó có thể là một ý tưởng tuyệt vời (tránh tai nạn và mã không thể đọc được tại các trang web cuộc gọi của bạn!) Để buộc sử dụng các đối số được đặt tên - threading.Threadlà một ví dụ. Hình thức đầu tiên là cách bạn triển khai nó trong Python 2.

Các thành ngữ là quan trọng đến nỗi bằng Python 3 nó bây giờ có cú pháp hỗ trợ đặc biệt: mọi lý luận sau khi một đơn *trong defchữ ký là từ khóa mà thôi, có nghĩa là, không thể được thông qua như là một đối số vị trí, nhưng chỉ như một tên. Vì vậy, trong Python 3, bạn có thể mã ở trên là:

def f(*, foo=None, bar=None, **kwargs):
    ...etc...

Thật vậy, trong Python 3, bạn thậm chí có thể có các đối số chỉ từ khóa không phải là tùy chọn (những đối số không có giá trị mặc định).

Tuy nhiên, Python 2 vẫn còn nhiều năm hoạt động lâu dài, vì vậy tốt hơn hết đừng quên các kỹ thuật và thành ngữ cho phép bạn thực hiện trong Python 2 ý tưởng thiết kế quan trọng được hỗ trợ trực tiếp bằng ngôn ngữ trong Python 3!


10
@Alex Martelli: Tôi chưa tìm thấy một câu trả lời nào cho rằng kwargs giống hệt với các đối số được đặt tên, chứ đừng nói là vượt trội. Nhưng diễn ngôn tốt về Py3k thay đổi, vì vậy +1
balpha

1
@Alex Martelli: cảm ơn rất nhiều vì câu trả lời của bạn, tôi đã học được rằng python 3 cho phép các đối số từ khóa bắt buộc, điều mà việc thiếu thường dẫn đến kiến ​​trúc không thỏa mãn trong mã và chức năng của tôi.
FloW

78

Tôi đề nghị một cái gì đó như thế này

def testFunc( **kwargs ):
    options = {
            'option1' : 'default_value1',
            'option2' : 'default_value2',
            'option3' : 'default_value3', }

    options.update(kwargs)
    print options

testFunc( option1='new_value1', option3='new_value3' )
# {'option2': 'default_value2', 'option3': 'new_value3', 'option1': 'new_value1'}

testFunc( option2='new_value2' )
# {'option1': 'default_value1', 'option3': 'default_value3', 'option2': 'new_value2'}

Và sau đó sử dụng các giá trị theo bất kỳ cách nào bạn muốn

dictionaryA.update(dictionaryB)thêm nội dung dictionaryBđể dictionaryAghi đè bất kỳ khóa trùng lặp.


2
Cảm ơn @AbhinavGupta! Chính xác những gì tôi đang tìm kiếm. Chỉ cần thêm for key in options: self.__setattr__(key, options[key])và tôi tốt để đi. :)
propjk007

53

Bạn sẽ làm

self.attribute = kwargs.pop('name', default_value)

hoặc là

self.attribute = kwargs.get('name', default_value)

Nếu bạn sử dụng pop, thì bạn có thể kiểm tra xem có bất kỳ giá trị giả nào được gửi không và thực hiện hành động thích hợp (nếu có).


2
Bạn có thể làm rõ ý của bạn bằng cách đề xuất .popsẽ giúp bạn kiểm tra xem có bất kỳ giá trị giả nào được gửi không?
Alan H.

13
@Alan H.: Nếu có bất cứ điều gì còn sót lại trong kwargs sau khi tất cả các popping được thực hiện, thì bạn đã có các giá trị giả.
Vinay Sajip

@VinaySajip: Ok, đó là một điểm tuyệt vời trên .pop "vs" .get, nhưng tôi vẫn không hiểu tại sao pop thích hợp hơn các đối số được đặt tên, bên cạnh việc buộc người gọi không sử dụng các tham số vị trí.
MestreLion

1
@MestreLion: Nó phụ thuộc vào số lượng đối số từ khóa mà API của bạn cho phép. Tôi không cho rằng đề xuất của tôi tốt hơn các đối số được đặt tên, nhưng Python cho phép bạn nắm bắt các đối số không tên kwargsvì một lý do.
Vinay Sajip

Vì vậy, chỉ cần kiểm tra. Pop có trả về giá trị từ điển nếu khóa tồn tại và nếu không, nó sẽ trả về default_valuethông qua? Và loại bỏ chìa khóa đó sau đó?
Daniel Möller

42

Sử dụng ** kwargs và các giá trị mặc định rất dễ dàng. Tuy nhiên, đôi khi, bạn không nên sử dụng ** kwargs ở nơi đầu tiên.

Trong trường hợp này, chúng tôi không thực sự sử dụng ** kwargs tốt nhất.

class ExampleClass( object ):
    def __init__(self, **kwargs):
        self.val = kwargs.get('val',"default1")
        self.val2 = kwargs.get('val2',"default2")

Trên đây là một "tại sao phải bận tâm?" tờ khai. Nó giống như

class ExampleClass( object ):
    def __init__(self, val="default1", val2="default2"):
        self.val = val
        self.val2 = val2

Khi bạn đang sử dụng ** kwargs, bạn có nghĩa là một từ khóa không chỉ là tùy chọn, mà còn có điều kiện. Có các quy tắc phức tạp hơn các giá trị mặc định đơn giản.

Khi bạn đang sử dụng ** kwargs, bạn thường có nghĩa giống như sau, trong đó mặc định đơn giản không áp dụng.

class ExampleClass( object ):
    def __init__(self, **kwargs):
        self.val = "default1"
        self.val2 = "default2"
        if "val" in kwargs:
            self.val = kwargs["val"]
            self.val2 = 2*self.val
        elif "val2" in kwargs:
            self.val2 = kwargs["val2"]
            self.val = self.val2 / 2
        else:
            raise TypeError( "must provide val= or val2= parameter values" )

Tôi thích cái brainteaser nhỏ đó! Tôi cứ nghĩ, "Nhưng bạn chỉ có thể sử dụng get hoặc pop với - ồ, họ đồng phụ thuộc ..."
trojjer

28

**kwargsđược sử dụng khi số lượng đối số không xác định, tại sao không làm điều này?

class Exampleclass(object):
  def __init__(self, **kwargs):
    for k in kwargs.keys():
       if k in [acceptable_keys_list]:
          self.__setattr__(k, kwargs[k])

vâng, điều này thanh lịch và mạnh mẽ ... không quá chắc chắn về dấu ngoặc vuông xung quanh accept_keys_list: Tôi sẽ tạo một danh sách này hoặc một danh sách và sau đó thả các dấu ngoặc đó vào câu lệnh "if"
loài gặm nhấm

Tôi đã sửa đổi một chút cho trường hợp này khi tất cả các khóa được mong đợi: stackoverflow.com/questions/1098549/NH
rebelliard

14

Đây là một cách tiếp cận khác:

def my_func(arg1, arg2, arg3):
    ... so something ...

kwargs = {'arg1': 'Value One', 'arg2': 'Value Two', 'arg3': 'Value Three'}
# Now you can call the function with kwargs like this:

my_func(**kwargs)

Được sử dụng rất nhiều trong Django CBVs (ví dụ get_form_kwargs()). ccbv.co.uk/projects/Django/1.5/django.view.generic.edit/ cường
trojjer

Các get_form()chương trình phương pháp làm thế nào để có được tranh luận rộng rãi bởi các từ khóa trì hoãn đến một phương pháp khác ( get_form_kwargsnhư đã đề cập ở trên). Nó khởi tạo các hình thức như sau : form_class(**self.get_form_kwargs()).
trojjer

Sau đó, thật dễ dàng để ghi đè get_form_kwargs()trong chế độ xem lớp con và thêm / xóa kwargs dựa trên logic cụ thể. Nhưng đó là cho một hướng dẫn Django.
trojjer

12

Tôi nghĩ rằng cách thích hợp để sử dụng **kwargstrong Python khi nói đến các giá trị mặc định là sử dụng phương thức từ điển setdefault, như được đưa ra dưới đây:

class ExampleClass:
    def __init__(self, **kwargs):
        kwargs.setdefault('val', value1)
        kwargs.setdefault('val2', value2)

Theo cách này, nếu người dùng vượt qua 'val' hoặc 'val2' trong từ khóa args, họ sẽ được sử dụng; mặt khác, các giá trị mặc định đã được đặt sẽ được sử dụng.


11

Bạn có thể làm một cái gì đó như thế này

class ExampleClass:
    def __init__(self, **kwargs):
        arguments = {'val':1, 'val2':2}
        arguments.update(kwargs)
        self.val = arguments['val']
        self.val2 = arguments['val2']

11

Theo dõi đề xuất @srhegde về việc sử dụng setattr :

class ExampleClass(object):
    __acceptable_keys_list = ['foo', 'bar']

    def __init__(self, **kwargs):
        [self.__setattr__(key, kwargs.get(key)) for key in self.__acceptable_keys_list]

Biến thể này hữu ích khi lớp dự kiến ​​sẽ có tất cả các mục trong acceptabledanh sách của chúng tôi .


1
Đó không phải là trường hợp sử dụng để hiểu danh sách, bạn nên sử dụng vòng lặp for trong phương thức init của mình.
ettanany

5

Nếu bạn muốn kết hợp điều này với * args, bạn phải giữ * args và ** kwargs ở cuối định nghĩa.

Vì thế:

def method(foo, bar=None, *args, **kwargs):
    do_something_with(foo, bar)
    some_other_function(*args, **kwargs)

1

@AbhinavGupta và @Steef đã đề xuất sử dụng update(), điều mà tôi thấy rất hữu ích để xử lý danh sách đối số lớn:

args.update(kwargs)

Điều gì xảy ra nếu chúng tôi muốn kiểm tra xem người dùng đã không vượt qua bất kỳ đối số giả / không được hỗ trợ nào? @VinaySajip chỉ ra rằng pop()có thể được sử dụng để xử lý lặp lại danh sách các đối số. Sau đó, bất kỳ đối số còn lại là giả mạo. Đẹp.

Đây là một cách có thể khác để làm điều này, đó là giữ cú pháp đơn giản của việc sử dụng update():

# kwargs = dictionary of user-supplied arguments
# args = dictionary containing default arguments

# Check that user hasn't given spurious arguments
unknown_args = user_args.keys() - default_args.keys()
if unknown_args:
    raise TypeError('Unknown arguments: {}'.format(unknown_args))

# Update args to contain user-supplied arguments
args.update(kwargs)

unknown_argslà một settên chứa các đối số không xảy ra trong mặc định.


0

Một giải pháp đơn giản khác để xử lý các đối số chưa biết hoặc nhiều đối số có thể là:

class ExampleClass(object):

    def __init__(self, x, y, **kwargs):
      self.x = x
      self.y = y
      self.attributes = kwargs

    def SomeFunction(self):
      if 'something' in self.attributes:
        dosomething()

0

** kwargs cho phép tự do thêm bất kỳ số lượng đối số từ khóa. Người ta có thể có một danh sách các khóa mà anh ta có thể đặt các giá trị mặc định. Nhưng việc thiết lập các giá trị mặc định cho số lượng khóa không xác định có vẻ không cần thiết. Cuối cùng, điều quan trọng là có các khóa làm thuộc tính thể hiện. Vì vậy, tôi sẽ làm điều này như sau:

class Person(object):
listed_keys = ['name', 'age']

def __init__(self, **kwargs):
    _dict = {}
    # Set default values for listed keys
    for item in self.listed_keys: 
        _dict[item] = 'default'
    # Update the dictionary with all kwargs
    _dict.update(kwargs)

    # Have the keys of kwargs as instance attributes
    self.__dict__.update(_dict)
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.