Cara yang tepat untuk menggunakan ** kwargs dengan Python

454

Apa cara yang tepat untuk digunakan **kwargsdalam Python ketika datang ke nilai default?

kwargsmengembalikan kamus, tetapi apa cara terbaik untuk menetapkan nilai default, atau ada satu? Haruskah saya mengaksesnya sebagai kamus? Gunakan fungsi get?

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

Sebuah pertanyaan sederhana, tetapi satu yang saya tidak dapat menemukan sumber daya yang baik. Orang melakukannya dengan cara berbeda dalam kode yang saya lihat dan sulit untuk mengetahui apa yang harus digunakan.

Kekoa
sumber

Jawaban:

468

Anda dapat memberikan nilai default get()untuk kunci yang tidak ada dalam kamus:

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

Namun, jika Anda berencana menggunakan argumen tertentu dengan nilai default tertentu, mengapa tidak menggunakan argumen bernama di tempat pertama?

def __init__(self, val2="default value", **kwargs):
balpha
sumber
16
Saya suka menggunakan argumen posisi hanya untuk argumen yang diperlukan, dan kwarg untuk argumen yang mungkin atau mungkin tidak ditentukan, tetapi akan membantu jika memiliki nilai default. kwargs bagus karena Anda dapat mengirimkan argumen Anda dalam urutan apa pun yang Anda pilih. Argumen posisi tidak memberi Anda kebebasan itu.
Kekoa
95
Anda bisa memberikan argumen bernama dalam urutan apa pun yang Anda suka. Anda hanya perlu mematuhi posisi jika Anda tidak menggunakan nama - yang dalam hal kwarg, Anda harus melakukannya. Sebaliknya, menggunakan argumen bernama sebagai lawan dari kwargs memberi Anda kebebasan tambahan untuk tidak menggunakan nama - maka, bagaimanapun, Anda harus menjaga urutannya.
balpha
13
@Kekoa: Anda selalu dapat mengirimkan argumen bernama dalam urutan apa pun yang Anda pilih. Anda tidak harus menggunakan ** kwargs untuk mendapatkan fleksibilitas ini.
S.Lott
13
pylint menandainya sebagai bentuk buruk untuk menggunakan kwargs di __init__(). Adakah yang bisa menjelaskan mengapa ini pelanggaran yang tidak patut?
hughdbrown
271

Sementara sebagian besar jawaban mengatakan bahwa, misalnya,

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

sama dengan"

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

ini tidak benar. Dalam kasus terakhir, fbisa disebut sebagai f(23, 42), sementara kasus yang pertama menerima suatu argumen bernama hanya - tidak ada panggilan posisional. Seringkali Anda ingin memberikan fleksibilitas maksimum kepada penelepon dan karena itu bentuk kedua, seperti yang dinyatakan sebagian besar jawaban, lebih disukai: tetapi itu tidak selalu terjadi. Ketika Anda menerima banyak parameter opsional yang biasanya hanya sedikit yang dilewati, mungkin ide yang bagus (menghindari kecelakaan dan kode yang tidak dapat dibaca di situs panggilan Anda!) Untuk memaksa penggunaan argumen yang disebutkan - threading.Threadadalah contohnya. Bentuk pertama adalah bagaimana Anda menerapkannya di Python 2.

Ungkapan ini sangat penting sehingga dalam Python 3 sekarang memiliki sintaks pendukung khusus: setiap argumen setelah satu *di deftanda tangan adalah kata kunci saja, yaitu, tidak dapat dilewatkan sebagai argumen posisi, tetapi hanya sebagai yang bernama. Jadi dalam Python 3 Anda bisa kode di atas sebagai:

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

Memang, dalam Python 3 Anda bahkan dapat memiliki argumen hanya kata kunci yang bukan opsional (yang tanpa nilai default).

Namun, Python 2 masih memiliki tahun-tahun panjang kehidupan produktif di masa depan, jadi lebih baik untuk tidak melupakan teknik dan idiom yang memungkinkan Anda menerapkan dalam Python 2 ide-ide desain penting yang secara langsung didukung dalam bahasa di Python 3!

Alex Martelli
sumber
10
@Alex Martelli: Saya belum menemukan jawaban tunggal yang mengklaim kwarg identik dengan argumen bernama, apalagi unggul. Tapi wacana yang bagus untuk perubahan Py3k, jadi +1
balpha
1
@Alex Martelli: terima kasih banyak atas jawaban Anda, saya mengetahui bahwa python 3 memungkinkan argumen-kata kunci wajib, yang kekurangannya sering mengakibatkan arsitektur yang tidak memuaskan dalam kode dan fungsi saya.
FloW
78

Saya menyarankan sesuatu seperti ini

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

Dan kemudian gunakan nilai apa pun yang Anda inginkan

dictionaryA.update(dictionaryB)menambahkan konten dictionaryBuntuk dictionaryAmenimpa kunci duplikat.

Abhinav Gupta
sumber
2
Terima kasih @AbhinavGupta! Persis apa yang saya cari. Baru ditambahkan for key in options: self.__setattr__(key, options[key])dan saya siap berangkat. :)
propjk007
53

Anda akan melakukannya

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

atau

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

Jika Anda menggunakan pop, maka Anda dapat memeriksa apakah ada nilai palsu yang dikirim, dan mengambil tindakan yang sesuai (jika ada).

Vinay Sajip
sumber
2
Bisakah Anda mengklarifikasi apa yang Anda maksud dengan menyarankan .popakan membantu Anda “memeriksa apakah ada nilai palsu yang dikirim”?
Alan H.
13
@Lan H .: jika ada sesuatu yang tersisa di kwargs setelah semua popping dilakukan, maka Anda mendapatkan nilai palsu.
Vinay Sajip
@VinaySajip: Ok, itu poin yang bagus di .pop "vs". Get, tapi saya masih tidak melihat mengapa pop lebih disukai daripada argumen bernama, selain memaksa pemanggil untuk tidak menggunakan parameter posisi.
MestreLion
1
@MestreLion: Tergantung pada berapa banyak argumen kata kunci yang diizinkan oleh API Anda. Saya tidak mengklaim bahwa saran saya lebih baik daripada argumen yang disebutkan, tetapi Python memungkinkan Anda untuk menangkap argumen tanpa nama kwargskarena suatu alasan.
Vinay Sajip
Jadi, hanya memeriksa. Apakah pop mengembalikan nilai kamus jika kunci ada dan jika tidak mengembalikan default_valuelulus? Dan menghapus kunci itu sesudahnya?
Daniel Möller
42

Menggunakan ** kwargs dan nilai default mudah. Kadang-kadang, bagaimanapun, Anda seharusnya tidak menggunakan ** kwargs di tempat pertama.

Dalam hal ini, kami tidak benar-benar memanfaatkan ** kwargs.

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

Di atas adalah "mengapa repot-repot?" pernyataan. Itu sama dengan

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

Ketika Anda menggunakan ** kwargs, Anda berarti bahwa kata kunci tidak hanya opsional, tetapi juga kondisional. Ada aturan yang lebih kompleks daripada nilai standar sederhana.

Ketika Anda menggunakan ** kwargs, Anda biasanya memiliki makna lebih seperti yang berikut, di mana standar sederhana tidak berlaku.

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" )
S.Lott
sumber
Saya suka brainteaser kecil itu! Saya terus berpikir, "Tapi Anda bisa menggunakan get atau pop dengan - oh, mereka saling tergantung ..."
trojjer
28

Karena **kwargsdigunakan ketika jumlah argumen tidak diketahui, mengapa tidak melakukan ini?

class Exampleclass(object):
  def __init__(self, **kwargs):
    for k in kwargs.keys():
       if k in [acceptable_keys_list]:
          self.__setattr__(k, kwargs[k])
srhegde
sumber
ya, ini elegan dan kuat ... meskipun tidak terlalu yakin tentang tanda kurung di sekitar accept_keys_list: saya akan membuat ini tuple atau daftar dan kemudian menjatuhkan tanda kurung itu di pernyataan "jika"
mike rodent
Saya sedikit memodifikasi ini untuk kasus-kasus ketika semua kunci diharapkan: stackoverflow.com/questions/1098549/…
rebelliard
14

Inilah pendekatan lain:

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)
orokusaki
sumber
Banyak digunakan dalam Django CBVs (mis get_form_kwargs().). ccbv.co.uk/projects/Django/1.5/django.views.generic.edit/…
trojjer
The get_form()metode menunjukkan cara mendapatkan argumen kata kunci secara ekstensif dengan menunda ke metode lain ( get_form_kwargsseperti yang disebutkan di atas). Ini instantiates bentuk sebagai berikut: form_class(**self.get_form_kwargs()).
trojjer
Maka mudah untuk menimpa get_form_kwargs()dalam tampilan subkelas dan menambah / menghapus kwarg berdasarkan logika tertentu. Tapi itu untuk tutorial Django.
trojjer
12

Saya pikir cara yang tepat untuk digunakan **kwargsdalam Python ketika datang ke nilai default adalah dengan menggunakan metode kamus setdefault, seperti yang diberikan di bawah ini:

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

Dengan cara ini, jika pengguna memasukkan 'val' atau 'val2' pada kata kunci args, mereka akan digunakan; jika tidak, nilai default yang telah ditetapkan akan digunakan.

pengguna1930143
sumber
11

Anda bisa melakukan sesuatu seperti ini

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

Menindaklanjuti saran @srhegde untuk menggunakan 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]

Varian ini berguna ketika kelas diharapkan memiliki semua item dalam acceptabledaftar kami .

pemberontak
sumber
1
Itu bukan kasus penggunaan untuk pemahaman daftar, Anda harus menggunakan loop untuk dalam metode init Anda.
ettanany
5

Jika Anda ingin menggabungkan ini dengan * args Anda harus menyimpan * args dan ** kwargs di akhir definisi.

Begitu:

def method(foo, bar=None, *args, **kwargs):
    do_something_with(foo, bar)
    some_other_function(*args, **kwargs)
Jens Timmerman
sumber
1

@AbhinavGupta dan @Steef menyarankan untuk menggunakan update(), yang menurut saya sangat membantu untuk memproses daftar argumen besar:

args.update(kwargs)

Bagaimana jika kita ingin memeriksa bahwa pengguna belum memberikan argumen palsu / tidak didukung? @VinaySajip menunjukkan bahwa pop()dapat digunakan untuk memproses daftar argumen secara iteratif. Kemudian, setiap argumen sisa palsu. Bagus.

Berikut cara lain yang memungkinkan untuk melakukan ini, yang membuat sintaks sederhana penggunaan 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_argsadalah yang setberisi nama-nama argumen yang tidak terjadi di default.

pengguna20160
sumber
0

Solusi sederhana lain untuk memproses argumen yang tidak dikenal atau banyak dapat:

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()
tmsss
sumber
0

** kwargs memberikan kebebasan untuk menambahkan sejumlah argumen kata kunci. Seseorang mungkin memiliki daftar kunci yang dapat digunakan untuk menetapkan nilai default. Tetapi pengaturan nilai default untuk jumlah kunci yang tidak terbatas tampaknya tidak perlu. Akhirnya, mungkin penting untuk memiliki kunci sebagai atribut instance. Jadi, saya akan melakukan ini sebagai berikut:

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)
pengguna3503692
sumber