วิธีที่เหมาะสมในการใช้ ** kwargs ใน Python


454

วิธีที่เหมาะสมที่จะใช้**kwargsใน Python เมื่อมันมาถึงค่าเริ่มต้นคืออะไร?

kwargsส่งคืนพจนานุกรม แต่วิธีที่ดีที่สุดในการตั้งค่าเริ่มต้นคืออะไรหรือมีอย่างใดอย่างหนึ่ง ฉันควรจะเข้าถึงมันเป็นพจนานุกรมหรือไม่? ใช้ฟังก์ชั่นรับ?

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

คำถามง่ายๆ แต่เป็นคำถามที่ฉันไม่สามารถหาแหล่งข้อมูลที่ดีได้ ผู้คนทำวิธีต่าง ๆ ในรหัสที่ฉันเห็นและยากที่จะรู้ว่าควรใช้อะไร

คำตอบ:


468

คุณสามารถส่งค่าเริ่มต้นไปที่get()สำหรับคีย์ที่ไม่ได้อยู่ในพจนานุกรม:

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

อย่างไรก็ตามหากคุณวางแผนที่จะใช้อาร์กิวเมนต์ที่มีค่าเริ่มต้นเป็นพิเศษทำไมไม่ใช้อาร์กิวเมนต์ที่มีชื่อในตอนแรก

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

16
ฉันชอบที่จะใช้ข้อโต้แย้งตำแหน่งเฉพาะสำหรับข้อโต้แย้งที่จำเป็นและ kwargs สำหรับข้อโต้แย้งที่อาจจะหรืออาจจะไม่ได้ระบุ แต่มันจะมีประโยชน์ที่จะมีค่าเริ่มต้น kwargs นั้นดีเพราะคุณสามารถส่งบทความของคุณในลำดับใดก็ได้ที่คุณเลือก ข้อโต้แย้งตำแหน่งไม่ได้ให้อิสระแก่คุณ
Kekoa

95
คุณสามารถส่งผ่านข้อโต้แย้งที่มีชื่อในลำดับใดก็ได้ที่คุณต้องการ คุณจะต้องปฏิบัติตามตำแหน่งหากคุณไม่ใช้ชื่อ - ซึ่งในกรณีของ kwargs คุณต้อง แต่การใช้อาร์กิวเมนต์ที่มีชื่อซึ่งตรงข้ามกับ kwargs จะช่วยให้คุณมีอิสระเพิ่มเติมที่จะไม่ใช้ชื่อ - อย่างไรก็ตามคุณต้องรักษาลำดับ
balpha

13
@Kekoa: คุณสามารถส่งอาร์กิวเมนต์ที่มีชื่อในลำดับใดก็ได้ที่คุณเลือก คุณไม่จำเป็นต้องใช้ ** kwargs เพื่อรับความยืดหยุ่นนี้
S.Lott

13
ธง pylint __init__()มันเป็นรูปแบบที่ดีที่จะใช้ใน บางคนสามารถอธิบายได้ว่าทำไมการทำเช่นนี้จึงเป็นการละเมิดที่ไม่สมควร
hughdbrown


271

ในขณะที่คำตอบส่วนใหญ่บอกว่าเช่น

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

คือ "เหมือนกับ"

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

นี่ไม่เป็นความจริง. ในกรณีหลังfสามารถเรียกว่าเป็นf(23, 42)ในขณะที่กรณีเดิมยอมรับอาร์กิวเมนต์ที่มีชื่อเท่านั้น - ไม่มีการโทรตำแหน่ง บ่อยครั้งที่คุณต้องการอนุญาตให้ผู้เรียกมีความยืดหยุ่นสูงสุดดังนั้นรูปแบบที่สองซึ่งเป็นคำตอบที่ยืนยันได้ส่วนใหญ่จึงเป็นที่นิยม: แต่นั่นไม่ใช่กรณีเสมอไป เมื่อคุณยอมรับพารามิเตอร์ทางเลือกจำนวนมากซึ่งโดยทั่วไปจะมีเพียงไม่กี่คนที่ผ่านมามันอาจเป็นความคิดที่ยอดเยี่ยม (หลีกเลี่ยงอุบัติเหตุและรหัสที่อ่านไม่ได้ที่ไซต์การโทรของคุณ!) เพื่อบังคับให้ใช้อาร์กิวเมนต์ที่มีชื่อ - threading.Threadเป็นตัวอย่าง แบบฟอร์มแรกคือวิธีที่คุณใช้งานใน Python 2

สำนวนนั้นสำคัญมากใน Python 3 ตอนนี้มันมีรูปแบบการสนับสนุนพิเศษ: ทุก ๆ อาร์กิวเมนต์หลังจากหนึ่ง*ในdefลายเซ็นคือคีย์เวิร์ดเท่านั้นนั่นคือไม่สามารถส่งผ่านเป็นอาร์กิวเมนต์ตำแหน่ง แต่เป็นชื่อที่กำหนดเท่านั้น ดังนั้นใน Python 3 คุณสามารถโค้ดข้างต้นเป็น:

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

แท้จริงแล้วใน Python 3 คุณสามารถมีอาร์กิวเมนต์ที่เป็นคีย์เวิร์ดเท่านั้นซึ่งไม่ใช่ตัวเลือก (อันที่ไม่มีค่าเริ่มต้น)

อย่างไรก็ตาม Python 2 ยังมีชีวิตอีกหลายปีข้างหน้าดังนั้นมันจะดีกว่าที่จะไม่ลืมเทคนิคและสำนวนที่ให้คุณใช้ใน Python 2 แนวคิดการออกแบบที่สำคัญที่ได้รับการสนับสนุนโดยตรงในภาษาใน Python 3!


10
@Alex Martelli: ฉันไม่พบคำตอบเดียวที่อ้างว่า kwargs เหมือนกับอาร์กิวเมนต์ที่มีชื่อ แต่วาทกรรมที่ดีที่จะ Py3k เปลี่ยนแปลงดังนั้น 1
balpha

1
@Alex Martelli: ขอบคุณมากสำหรับคำตอบของคุณมันได้เรียนรู้ว่า python 3 อนุญาตให้มีการขัดแย้งกับคำหลักบังคับซึ่งการขาดมักจะทำให้สถาปัตยกรรมไม่พอใจในรหัสและฟังก์ชั่นของฉัน
FloW

78

ฉันแนะนำสิ่งนี้

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

จากนั้นใช้ค่าตามที่คุณต้องการ

dictionaryA.update(dictionaryB)เพิ่มเนื้อหาของdictionaryBการdictionaryAเขียนทับคีย์ที่ซ้ำกัน


2
ขอบคุณ @AbhinavGupta! สิ่งที่ฉันกำลังมองหา เพิ่งเพิ่มfor key in options: self.__setattr__(key, options[key])และฉันดีไป :)
propjk007

53

คุณต้องการ

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

หรือ

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

หากคุณใช้popคุณสามารถตรวจสอบว่ามีค่าเก๊บใด ๆ ที่ส่งและดำเนินการที่เหมาะสม (ถ้ามี)


2
คุณสามารถอธิบายสิ่งที่คุณหมายถึงโดยการแนะนำ.popจะช่วยให้คุณ "ตรวจสอบว่ามีการส่งค่าปลอม ๆ "?
Alan H.

13
@ อลันเอช: หากมีสิ่งใดหลงเหลืออยู่ใน kwargs หลังจากการ popping เสร็จสิ้นแสดงว่าคุณมีค่าที่ไม่จริง
Vinay Sajip

@VinaySajip: ตกลงนั่นเป็นจุดที่ยอดเยี่ยมสำหรับ. pop "vs" .get แต่ฉันก็ยังไม่เห็นว่าทำไม pop ถึงดีกว่าอาร์กิวเมนต์ที่มีชื่อนอกเหนือจากการบังคับให้ผู้โทรไม่ใช้พารามิเตอร์ตำแหน่ง
MestreLion

1
@MestreLion: ขึ้นอยู่กับจำนวนอาร์กิวเมนต์ของคำหลักที่ API ของคุณอนุญาต ฉันไม่อ้างว่าข้อเสนอแนะของฉันดีกว่าการโต้แย้งที่ตั้งชื่อไว้ แต่ Python ช่วยให้คุณสามารถจับข้อโต้แย้งที่ไม่มีชื่อได้kwargsด้วยเหตุผล
Vinay Sajip

ดังนั้นเพียงแค่ตรวจสอบ ป๊อปส่งคืนค่าพจนานุกรมหรือไม่หากมีคีย์อยู่และหากไม่ส่งคืนค่าที่default_valueส่งให้ และลบคีย์นั้นในภายหลังหรือไม่
Daniel Möller

43

การใช้ ** kwargs และค่าเริ่มต้นนั้นง่ายมาก อย่างไรก็ตามบางครั้งคุณไม่ควรใช้ ** kwargs ตั้งแต่แรก

ในกรณีนี้เราไม่ได้ใช้ kwargs ให้ดีที่สุด

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

ข้างต้นคือ "ทำไมต้องรำคาญ" การประกาศ มันเป็นเช่นเดียวกับ

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

เมื่อคุณใช้ ** kwargs คุณหมายถึงว่าคำหลักไม่ได้เป็นเพียงตัวเลือก แต่มีเงื่อนไข มีกฎที่ซับซ้อนกว่าค่าเริ่มต้นธรรมดา

เมื่อคุณใช้ ** kwargs คุณมักจะหมายถึงสิ่งต่อไปนี้มากขึ้นโดยที่ค่าเริ่มต้นไม่สามารถใช้ได้

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

ฉันชอบนักคิดน้อยคนนั้น! ฉันคิดอยู่เสมอว่า "แต่คุณสามารถใช้รับหรือป๊อป - โอ้พวกเขาพึ่งพากัน ... "
โทรจัน

28

เนื่องจาก**kwargsใช้เมื่อไม่ทราบจำนวนข้อโต้แย้งทำไมไม่ทำเช่นนี้

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

ใช่นี่คือสง่างามและมีประสิทธิภาพ ... ไม่แน่ใจเกี่ยวกับวงเล็บเหลี่ยมรอบที่ยอมรับได้ _keys_list แม้ว่า: ฉันจะทำให้ tuple หรือรายการแล้ววางวงเล็บเหล่านั้นในคำสั่ง "if"
ไมค์หนู

ฉันแก้ไขสิ่งนี้เล็กน้อยสำหรับกรณีที่คาดว่าจะได้รับกุญแจทั้งหมด: stackoverflow.com/questions/1098549/…
rebelliard

14

นี่เป็นวิธีการอื่น:

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)

ใช้มากใน Django CBVs (เช่นget_form_kwargs()) ccbv.co.uk/projects/Django/1.5/django.views.generic.edit/...
trojjer

get_form()วิธีการแสดงให้เห็นถึงวิธีการที่จะได้รับข้อโต้แย้งคำหลักอย่างกว้างขวางโดยการชะลอการวิธีอื่น ( get_form_kwargsตามที่กล่าวไว้ข้างต้น) มันยกตัวอย่างแบบฟอร์มดังต่อไปนี้: form_class(**self.get_form_kwargs()).
trojjer

จากนั้นมันจะง่ายต่อการแทนที่get_form_kwargs()ในมุมมองคลาสย่อยและเพิ่ม / ลบ kwargs ตามตรรกะเฉพาะ แต่สำหรับกวดวิชา Django
trojjer

12

ฉันคิดว่าวิธีที่เหมาะสมที่จะใช้**kwargsใน Python เมื่อมันมาถึงค่าเริ่มต้นคือการใช้วิธีการพจนานุกรมsetdefaultตามที่ระบุด้านล่าง:

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

ด้วยวิธีนี้หากผู้ใช้ผ่าน 'val' หรือ 'val2' ในคำหลักargsพวกเขาจะถูกนำมาใช้; มิฉะนั้นจะใช้ค่าเริ่มต้นที่ตั้งค่าไว้


11

คุณสามารถทำอะไรเช่นนี้

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

11

ติดตามข้อเสนอแนะ@srhegdeของการใช้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]

ตัวแปรนี้จะเป็นประโยชน์เมื่อระดับที่คาดว่าจะมีรายการทั้งหมดของเราในacceptableรายการ


1
นั่นไม่ใช่กรณีการใช้งานสำหรับความเข้าใจในรายการคุณควรใช้ห่วงสำหรับวิธีการเริ่มต้นของคุณ
ettanany

5

หากคุณต้องการรวมสิ่งนี้กับ * args คุณต้องเก็บ * args และ ** kwargs ไว้ท้ายคำจำกัดความ

ดังนั้น:

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

1

@AbhinavGupta และ @Steef แนะนำให้ใช้update()ซึ่งฉันคิดว่ามีประโยชน์มากสำหรับการประมวลผลรายการอาร์กิวเมนต์ขนาดใหญ่:

args.update(kwargs)

ถ้าเราต้องการตรวจสอบว่าผู้ใช้ไม่ได้ผ่านการโต้แย้งปลอม / ไม่สนับสนุน? @VinaySajip ชี้ให้เห็นว่าpop()สามารถใช้เพื่อประมวลผลรายการอาร์กิวเมนต์ซ้ำ ๆ จากนั้นอาร์กิวเมนต์ที่เหลือใด ๆ จะเป็นของปลอม ดี

นี่เป็นอีกวิธีที่เป็นไปได้ในการทำเช่นนี้ซึ่งทำให้ไวยากรณ์การใช้งานง่าย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_argsเป็นsetชื่อที่มีอาร์กิวเมนต์ที่ไม่ได้เกิดขึ้นในค่าเริ่มต้น


0

อีกวิธีที่ง่ายสำหรับการประมวลผลอาร์กิวเมนต์ที่ไม่รู้จักหรือหลายอาร์กิวเมนต์สามารถ:

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 ให้อิสระในการเพิ่มอาร์กิวเมนต์ของคำหลักจำนวนเท่าใดก็ได้ หนึ่งอาจมีรายการของคีย์ที่เขาสามารถตั้งค่าเริ่มต้น แต่การตั้งค่าเริ่มต้นสำหรับจำนวนปุ่มไม่ จำกัด นั้นดูเหมือนไม่จำเป็น ในที่สุดอาจเป็นเรื่องสำคัญที่จะต้องใช้คีย์เป็นคุณลักษณะของอินสแตนซ์ ดังนั้นฉันจะทำดังนี้

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)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.