ʻif key ใน dict` เทียบกับ `try / except` - สำนวนไหนอ่านง่ายกว่ากัน?


95

ฉันมีคำถามเกี่ยวกับสำนวนและความสามารถในการอ่านและดูเหมือนว่าจะมีการปะทะกันของปรัชญา Python สำหรับกรณีนี้:

ฉันต้องการสร้างพจนานุกรม A จากพจนานุกรม B หากไม่มีคีย์เฉพาะใน B ให้ทำอะไรแล้วดำเนินการต่อ

ทางไหนดีกว่ากัน?

try:
    A["blah"] = B["blah"]
except KeyError:
    pass

หรือ

if "blah" in B:
    A["blah"] = B["blah"]

"ทำและขอการอภัย" เทียบกับ "ความเรียบง่ายและชัดเจน"

ไหนดีกว่ากันและทำไม?


1
ตัวอย่างที่สองอาจจะมีการเขียนที่ดีกว่าหรือif "blah" in B.keys() if B.has_key("blah")
girasquid

2
ไม่A.update(B)ได้ผลสำหรับคุณ?
SilentGhost

21
@Luke: has_keyได้รับการสนับสนุนinและตรวจสอบB.keys()การเปลี่ยนแปลงการดำเนินการ O (1) เป็น O (n)
kindall

4
@ ลุค: ไม่ใช่ไม่ใช่ .has_keyเลิกใช้งานและkeysสร้างรายการที่ไม่จำเป็นใน py2k และซ้ำซ้อนใน py3k
SilentGhost

2
'build' A เช่นเดียวกับ A ว่างเปล่าที่จะเริ่มต้นด้วย? และเราต้องการเพียงบางคีย์? ใช้ความเข้าใจ: A = dict((k, v) for (k, v) in B if we_want_to_include(k)).
Karl Knechtel

คำตอบ:


77

ข้อยกเว้นไม่ใช่เงื่อนไข

รุ่นเงื่อนไขชัดเจนกว่า นั่นเป็นเรื่องธรรมดา: นี่คือการควบคุมการไหลที่ตรงไปตรงมาซึ่งเป็นเงื่อนไขที่ออกแบบมาเพื่อไม่ใช่ข้อยกเว้น

เวอร์ชันข้อยกเว้นส่วนใหญ่จะใช้เป็นการเพิ่มประสิทธิภาพเมื่อทำการค้นหาเหล่านี้แบบวนซ้ำ: สำหรับอัลกอริทึมบางอย่างจะช่วยให้สามารถกำจัดการทดสอบจากลูปภายในได้ มันไม่มีประโยชน์ตรงนี้ มีข้อดีเล็กน้อยที่หลีกเลี่ยงไม่ต้องพูด"blah"ซ้ำสอง แต่ถ้าคุณทำสิ่งเหล่านี้มากคุณก็ควรมีmove_keyฟังก์ชันตัวช่วยอยู่ดี

โดยทั่วไปฉันขอแนะนำอย่างยิ่งให้ใช้เวอร์ชันที่มีเงื่อนไขเป็นค่าเริ่มต้นเว้นแต่คุณจะมีเหตุผลเฉพาะที่จะไม่ทำ Conditionals เป็นวิธีที่ชัดเจนในการทำเช่นนี้ซึ่งโดยปกติแล้วจะเป็นคำแนะนำที่ชัดเจนในการเลือกวิธีแก้ปัญหาหนึ่งมากกว่าอีกวิธีหนึ่ง


3
ฉันไม่เห็นด้วย ถ้าคุณพูดว่า "ทำ X และถ้าไม่ได้ผลให้ทำ Y" เหตุผลหลักในการแก้ปัญหาตามเงื่อนไขที่นี่คุณต้องเขียน"blah"บ่อยขึ้นซึ่งนำไปสู่สถานการณ์ที่ผิดพลาดได้ง่ายขึ้น
glglgl

6
และโดยเฉพาะอย่างยิ่งใน Python EAFP ถูกใช้กันอย่างแพร่หลาย
glglgl

8
คำตอบนี้จะถูกต้องสำหรับภาษาใด ๆ ที่ฉันรู้จักยกเว้น Python
Tomáš Zato - คืนสถานะ Monica

3
หากคุณใช้ข้อยกเว้นราวกับว่าเป็นเงื่อนไขใน Python ฉันหวังว่าจะไม่มีใครอ่านมัน
Glenn Maynard

แล้วคำตัดสินสุดท้ายคืออะไร? :)
floatingpurr

62

นอกจากนี้ยังมีวิธีที่สามที่หลีกเลี่ยงทั้งข้อยกเว้นและการค้นหาซ้ำซึ่งอาจมีความสำคัญหากการค้นหามีราคาแพง:

value = B.get("blah", None)
if value is not None: 
    A["blah"] = value

ในกรณีที่คุณคาดหวังในพจนานุกรมเพื่อให้มีNoneค่านิยมที่คุณสามารถใช้ค่าคงที่ลึกลับมากขึ้นบางอย่างเช่นNotImplemented, Ellipsisหรือสร้างใหม่:

MyConst = object()
def update_key(A, B, key):
    value = B.get(key, MyConst)
    if value is not MyConst: 
        A[key] = value

อย่างไรก็ตามการใช้update()เป็นตัวเลือกที่อ่านง่ายที่สุดสำหรับฉัน:

a.update((k, b[k]) for k in ("foo", "bar", "blah") if k in b)

14

จากสิ่งที่ฉันเข้าใจคุณต้องการอัปเดต dict A ด้วยคีย์คู่ค่าจาก dict B

update เป็นทางเลือกที่ดีกว่า

A.update(B)

ตัวอย่าง:

>>> A = {'a':1, 'b': 2, 'c':3}
>>> B = {'d': 2, 'b':5, 'c': 4}
>>> A.update(B)
>>> A
{'a': 1, 'c': 4, 'b': 5, 'd': 2}
>>> 

"หากไม่มีคีย์ใดคีย์หนึ่งใน B" ขออภัยควรจะชัดเจนกว่านี้ แต่ฉันต้องการคัดลอกเฉพาะค่าหากมีคีย์เฉพาะใน B เท่านั้น ไม่ใช่ทั้งหมดใน B.
LeeMobile

1
@LeeMobile -A.update({k: v for k, v in B.iteritems() if k in specificset})
Omnifarious

8

คำพูดโดยตรงจากวิกิพีเดีย Python:

ยกเว้นครั้งแรกทุกครั้งที่เห็นคำว่าการทดสอบคำสั่ง if ล้มเหลว หากคุณกำลังนับคำจำนวนมากหลายคำอาจเกิดขึ้นหลายครั้ง ในสถานการณ์ที่การเริ่มต้นของค่าจะเกิดขึ้นเพียงครั้งเดียวและการเพิ่มของค่านั้นจะเกิดขึ้นหลายครั้งการใช้คำสั่ง try จะถูกกว่า

ดังนั้นดูเหมือนว่าทั้งสองตัวเลือกจะทำงานได้ขึ้นอยู่กับสถานการณ์ สำหรับรายละเอียดเพิ่มเติมคุณอาจต้องการตรวจสอบลิงค์นี้: ลองยกเว้นประสิทธิภาพ


เป็นการอ่านที่น่าสนใจ แต่ฉันคิดว่าค่อนข้างไม่สมบูรณ์ คำสั่งที่ใช้มีเพียง 1 องค์ประกอบและฉันสงสัยว่าคำสั่งที่ใหญ่กว่าจะส่งผลกระทบอย่างมีนัยสำคัญต่อประสิทธิภาพ
user2682863

3

ฉันคิดว่ากฎทั่วไปที่นี่A["blah"]มักจะมีอยู่ถ้าเป็นเช่นนั้นการลองยกเว้นก็ดีถ้าไม่ใช้if "blah" in b:

ฉันคิดว่า "ลอง" ถูกตรงเวลา แต่ "ยกเว้น" แพงกว่า


10
อย่าเข้าใกล้โค้ดจากมุมมองการเพิ่มประสิทธิภาพโดยค่าเริ่มต้น เข้าใกล้จากมุมมองความสามารถในการอ่านและการบำรุงรักษา เว้นแต่เป้าหมายจะเป็นการเพิ่มประสิทธิภาพโดยเฉพาะนี่เป็นเกณฑ์ที่ไม่ถูกต้อง (และหากเป็นการเพิ่มประสิทธิภาพคำตอบคือการเปรียบเทียบไม่ใช่การคาดเดา)
Glenn Maynard

ฉันน่าจะใส่จุดสุดท้ายไว้ในวงเล็บหรือไม่ก็ vaguer - ประเด็นหลักของฉันคืออันแรกและฉันคิดว่ามันมีข้อดีเพิ่มเติมจากข้อที่สอง
นีล

3

ฉันคิดว่าตัวอย่างที่สองคือสิ่งที่คุณควรทำเว้นแต่รหัสนี้จะสมเหตุสมผล:

try:
    A["foo"] = B["foo"]
    A["bar"] = B["bar"]
    A["baz"] = B["baz"]
except KeyError:
    pass

Bเก็บไว้ในใจว่ารหัสจะยกเลิกทันทีที่มีเป็นกุญแจสำคัญที่ไม่ได้อยู่ใน หากรหัสนี้มีเหตุผลคุณควรใช้วิธีการยกเว้นหรือใช้วิธีทดสอบ ในความคิดของฉันเพราะมันสั้นกว่าและแสดงเจตนาอย่างชัดเจนจึงอ่านง่ายกว่าวิธีการยกเว้นมาก

แน่นอนว่าคนที่บอกให้คุณใช้updateนั้นถูกต้อง หากคุณใช้ Python เวอร์ชันที่รองรับความเข้าใจในพจนานุกรมฉันขอแนะนำรหัสนี้:

updateset = {'foo', 'bar', 'baz'}
A.update({k: B[k] for k in updateset if k in B})

"โปรดทราบว่ารหัสจะยกเลิกทันทีที่มีคีย์ที่ไม่ได้อยู่ใน B" - นี่คือเหตุผลที่แนวทางปฏิบัติที่ดีที่สุดคือใส่ค่าต่ำสุดสัมบูรณ์ในการลองเท่านั้น: บล็อกโดยปกติจะเป็นบรรทัดเดียว ตัวอย่างแรกจะดีกว่าเมื่อเป็นส่วนหนึ่งของลูปเช่นfor key in ["foo", "bar", "baz"]: try: A[key] = B[key]
Zim

2

กฎในภาษาอื่นคือการสงวนข้อยกเว้นสำหรับเงื่อนไขพิเศษนั่นคือข้อผิดพลาดที่ไม่ได้เกิดขึ้นในการใช้งานปกติ ไม่ทราบว่ากฎนั้นใช้กับ Python อย่างไรเนื่องจากกฎนั้นไม่ควรมี StopIteration


ฉันคิดว่าเกาลัดนี้มาจากภาษาที่การจัดการข้อยกเว้นมีราคาแพงและอาจส่งผลกระทบอย่างมากต่อประสิทธิภาพ ฉันไม่เคยเห็นเหตุผลที่แท้จริงหรือเหตุผลเบื้องหลัง
John La Rooy

@JohnLaRooy ไม่ประสิทธิภาพไม่ใช่เหตุผลจริงๆ ข้อยกเว้นเป็นประเภทของgoto ที่ไม่ใช่ในพื้นที่ซึ่งบางคนคิดว่าจะขัดขวางความสามารถในการอ่านโค้ด อย่างไรก็ตามการใช้ข้อยกเว้นในลักษณะนี้ถือเป็นสำนวนใน Python ดังนั้นข้างต้นจึงไม่มีผล
Ian Goldby

ผลตอบแทนตามเงื่อนไขยังเป็น "โกโตะที่ไม่ใช่ในพื้นที่" และหลายคนชอบรูปแบบนั้นแทนที่จะตรวจสอบทหารรักษาการณ์ที่ส่วนท้ายของบล็อกโค้ด
cowbert

1

โดยส่วนตัวฉันเอนเอียงไปทางวิธีที่สอง (แต่ใช้has_key):

if B.has_key("blah"):
  A["blah"] = B["blah"]

ด้วยวิธีนี้การดำเนินการมอบหมายแต่ละครั้งจะมีเพียงสองบรรทัด (แทนที่จะเป็น 4 ด้วย try / except) และข้อยกเว้นใด ๆ ที่เกิดขึ้นจะเป็นข้อผิดพลาดจริงหรือสิ่งที่คุณพลาดไป (แทนที่จะพยายามเข้าถึงคีย์ที่ไม่มี) .

ปรากฎว่า (ดูความคิดเห็นในคำถามของคุณ) has_keyเลิกใช้งานแล้ว - ดังนั้นฉันเดาว่าควรเขียนเป็น

if "blah" in B:
  A["blah"] = B["blah"]

1

เริ่มต้นPython 3.8และการแนะนำนิพจน์การกำหนด (PEP 572) ( :=ตัวดำเนินการ) เราสามารถจับค่าเงื่อนไขdictB.get('hello', None)ในตัวแปรvalueเพื่อตรวจสอบว่าไม่ใช่None(เป็นdict.get('hello', None)ค่าที่ส่งกลับค่าที่เกี่ยวข้องหรือNone) จากนั้นใช้ภายในเนื้อความของ เงื่อนไข:

# dictB = {'hello': 5, 'world': 42}
# dictA = {}
if value := dictB.get('hello', None):
  dictA["hello"] = value
# dictA is now {'hello': 5}

1
สิ่งนี้จะล้มเหลวหาก value == 0
Eric

0

แม้ว่าคำตอบที่ได้รับการยอมรับจะเน้นที่หลักการ "ดูก่อนกระโดด" อาจใช้ได้กับภาษาส่วนใหญ่ แต่ pythonic มากกว่าอาจเป็นแนวทางแรกตามหลักการของ python ไม่ต้องพูดถึงมันเป็นรูปแบบการเข้ารหัสที่ถูกต้องใน python สิ่งสำคัญคือตรวจสอบให้แน่ใจว่าคุณใช้ try except block ในบริบทที่ถูกต้องและปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุด เช่น. ทำหลายสิ่งมากเกินไปในบล็อกทดลองจับข้อยกเว้นที่กว้างมากหรือแย่กว่านั้นคือ bare except clause เป็นต้น

ขอการให้อภัยได้ง่ายกว่าการอนุญาต (EAFP)

ดูเอกสารหลามอ้างอิงที่นี่

นอกจากนี้บล็อกนี้จาก Brett ซึ่งเป็นหนึ่งในนักพัฒนาหลักได้สัมผัสส่วนใหญ่โดยสรุป

ดูการอภิปรายอื่น ๆที่นี่ :

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