ใช้ try vs if ใน python


145

มีเหตุผลในการตัดสินใจว่าจะใช้หนึ่งtryหรือifโครงสร้างที่จะใช้เมื่อการทดสอบตัวแปรที่จะมีค่าหรือไม่?

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

result = function();
if (result):
    for r in result:
        #process items

หรือ

result = function();
try:
    for r in result:
        #process items
except TypeError:
    pass;

การอภิปรายที่เกี่ยวข้อง:

ตรวจสอบการมีอยู่ของสมาชิกใน Python


โปรดจำไว้ว่าหากรายการ #process stanza ของคุณมีแนวโน้มที่จะโยน TypeError คุณต้องลองอีกครั้งยกเว้น: block สำหรับตัวอย่างเฉพาะนี้ฉันเพียงแค่ใช้ if:
richo

คำตอบ:


237

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

ในตัวอย่างของคุณ (พูดว่าแทนที่จะส่งคืนรายการหรือสตริงว่างเปล่าฟังก์ชันจะส่งคืนรายการหรือNone) ถ้าคุณคาดหวังว่า 99% ของเวลาresultจะมีสิ่งที่ทำซ้ำได้จริงฉันจะใช้try/exceptวิธีนี้ มันจะเร็วขึ้นถ้ามีข้อยกเว้นพิเศษจริงๆ ถ้าresultเป็นNoneกว่า 50% ของเวลาแล้วใช้ifอาจจะดีกว่าคือ

เพื่อรองรับสิ่งนี้ด้วยการวัดเพียงเล็กน้อย:

>>> import timeit
>>> timeit.timeit(setup="a=1;b=1", stmt="a/b") # no error checking
0.06379691968322732
>>> timeit.timeit(setup="a=1;b=1", stmt="try:\n a/b\nexcept ZeroDivisionError:\n pass")
0.0829463709378615
>>> timeit.timeit(setup="a=1;b=0", stmt="try:\n a/b\nexcept ZeroDivisionError:\n pass")
0.5070195056614466
>>> timeit.timeit(setup="a=1;b=1", stmt="if b!=0:\n a/b")
0.11940114974277094
>>> timeit.timeit(setup="a=1;b=0", stmt="if b!=0:\n a/b")
0.051202772912802175

ดังนั้นในขณะที่ifคำสั่งจะมีค่าใช้จ่ายเสมอคุณเกือบจะเป็นอิสระในการตั้งค่าtry/exceptบล็อก แต่เมื่อExceptionเกิดขึ้นจริงค่าใช้จ่ายจะสูงขึ้นมาก

คุณธรรม:

  • มันใช้ได้อย่างสมบูรณ์ (และ "pythonic") เพื่อใช้try/exceptสำหรับการควบคุมการไหล
  • แต่มันสมเหตุสมผลที่สุดเมื่อExceptions นั้นยอดเยี่ยมจริง ๆ

จาก Python docs:

EAFP

ง่ายต่อการขอการอภัยมากกว่าการอนุญาต รูปแบบการเขียนแบบ Python ทั่วไปนี้จะสมมติว่ามีคีย์หรือคุณสมบัติที่ถูกต้องและจับข้อยกเว้นหากสมมติฐานพิสูจน์ว่าผิด สไตล์ที่สะอาดและรวดเร็วนี้โดดเด่นด้วยการปรากฏตัวของหลายคน tryและexceptงบ เทคนิคนี้ขัดแย้งกับ สไตล์LBYLซึ่งใช้ร่วมกับภาษาอื่น ๆ เช่น C


1
ขอบคุณ. ฉันเห็นว่าในกรณีนี้เหตุผลอาจเป็นความคาดหวังของผลลัพธ์
artdanil

6
.... และนั่นเป็นเหตุผลหนึ่งในหลายเหตุผลที่ทำให้การเพิ่มประสิทธิภาพ JIT สำหรับ Python เป็นไปได้ยาก เมื่อเร็ว ๆ นี้ในเบต้า LuaJIT 2 พิสูจน์แล้วภาษาไดนามิกสามารถเป็นจริงได้อย่างรวดเร็วจริงๆ แต่มันขึ้นอยู่กับการออกแบบภาษาเริ่มแรกและสไตล์ที่ส่งเสริม (ในบันทึกที่เกี่ยวข้องการออกแบบภาษาคือสาเหตุที่แม้แต่ JavaScirpt JIT ที่ดีที่สุดก็ไม่สามารถเปรียบเทียบกับ LuaJIT 1, น้อยกว่า 2)
Javier

1
@ 2rs2ts: ฉันเพิ่งกำหนดเวลาที่คล้ายกันด้วยตัวเอง ใน Python 3 try/exceptเร็วกว่า 25% if key in d:สำหรับกรณีที่กุญแจอยู่ในพจนานุกรม มันช้าลงมากเมื่อกุญแจไม่ได้อยู่ในพจนานุกรมอย่างที่คาดไว้และสอดคล้องกับคำตอบนี้
ทิม Pietzcker

5
ฉันรู้ว่านี่เป็นคำตอบเก่า แต่การใช้คำสั่งเช่น1/1กับ timeit ไม่ใช่ตัวเลือกที่ดีเพราะจะได้รับการปรับให้เหมาะสม (ดูdis.dis('1/1')และทราบว่าไม่มีการแบ่งเกิดขึ้น)
Andrea Corbellini

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

14

ฟังก์ชั่นของคุณไม่ควรส่งคืนชนิดผสม (เช่นรายการหรือสตริงว่าง) มันควรจะกลับรายการของค่าหรือเพียงแค่รายการที่ว่างเปล่า จากนั้นคุณไม่จำเป็นต้องทดสอบอะไรเช่นรหัสของคุณจะยุบไปที่:

for r in function():
    # process items

2
ฉันเห็นด้วยกับคุณในจุดนั้นอย่างสมบูรณ์ อย่างไรก็ตามฟังก์ชั่นไม่ใช่ของฉันและฉันแค่ใช้มัน
artdanil

2
@artdanil: ดังนั้นคุณสามารถห่อฟังก์ชั่นนั้นไว้ในสิ่งที่คุณเขียนนั่นได้ผลเช่นเดียวกับที่ Brandon Corfman กำลังคิด
quamrana

24
อันไหนที่เป็นคำถาม: เสื้อคลุมควรใช้ if หรือพยายามจัดการกรณีที่ไม่สามารถทำซ้ำได้?
jcdyer

@quamrana ซึ่งเป็นสิ่งที่ฉันพยายามจะทำ ดังนั้นตามที่ @ jcd ชี้ให้เห็นคำถามคือเกี่ยวกับวิธีที่ฉันควรจัดการกับสถานการณ์ในฟังก์ชันตัวตัดคำ
artdanil

12

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

ฉันสามารถสันนิษฐานได้ว่า "ไม่มีการคืนค่า" หมายถึงการคืนค่าเป็นไม่มีหรือไม่ ถ้าใช่หรือถ้า "ไม่มีค่า" เป็น False บูลีนที่ชาญฉลาดคุณสามารถทำสิ่งต่อไปนี้ได้เนื่องจากโค้ดของคุณจะถือว่า "ไม่มีค่า" เป็น "ห้ามทำซ้ำ" เป็นหลัก:

for r in function() or ():
    # process items

หากfunction()ส่งคืนบางสิ่งที่ไม่เป็นจริงคุณจะวนซ้ำสิ่งอันดับที่ว่างเปล่านั่นคือคุณจะไม่เรียกใช้การทำซ้ำ นี่คือ LBYL เป็นหลัก


4

ตัวอย่างที่สองของคุณใช้งานไม่ได้ - รหัสจะไม่ส่งข้อยกเว้น TypeError เนื่องจากคุณสามารถวนซ้ำทั้งสตริงและรายการ การวนซ้ำผ่านสตริงหรือรายการเปล่าก็ใช้ได้เช่นกัน - มันจะประมวลผลเนื้อความของลูปเป็นศูนย์ครั้ง


4

ข้อใดต่อไปนี้จะเป็นที่นิยมมากกว่าและเพราะอะไร

Look Before You Leap เป็นที่นิยมในกรณีนี้ ด้วยวิธีการยกเว้น, TypeError อาจเกิดขึ้นได้ทุกที่ในร่างกายของคุณและมันจะถูกจับและโยนออกไปซึ่งไม่ใช่สิ่งที่คุณต้องการและจะทำให้การแก้ไขข้อบกพร่อง

(ฉันเห็นด้วยกับ Brandon Corfman แต่: ส่งคืน None สำหรับ 'no items' แทนรายการว่างเปล่ามันเป็นนิสัยที่ไม่พึงประสงค์ของตัวแปลงสัญญาณ Java ที่ไม่ควรเห็นใน Python หรือ Java)


4

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

ฉันมีในใจสถานการณ์ (ร่วมกัน):

# keep access counts for different files
file_counts={}
...
# got a filename somehow
if filename not in file_counts:
    file_counts[filename]=0
file_counts[filename]+=1

แทนที่จะเทียบเท่า:

...
try:
    file_counts[filename]+=1
except KeyError:
    file_counts[filename]=1

นี่คือตัวอย่างของความแตกต่างระหว่างวิธีการที่ Tim Pietzcker กล่าวถึง: ประการแรกคือ LBYL, ที่สองคือ EAFP
Managu

เพียงแค่บันทึกย่อที่++ไม่สามารถใช้งานได้กับงู+= 1ใหญ่
tgray

3

Bobinceชี้ให้เห็นอย่างชาญฉลาดว่าการตัดเคสที่สองสามารถจับ TypeErrors ในลูปได้ซึ่งไม่ใช่สิ่งที่คุณต้องการ หากคุณต้องการลองใช้จริง ๆ คุณสามารถทดสอบว่าสามารถทำซ้ำก่อนลูปได้หรือไม่

result = function();
try:
    it = iter(result)
except TypeError:
    pass
else:
    for r in it:
        #process items

อย่างที่คุณเห็นมันค่อนข้างน่าเกลียด ฉันไม่แนะนำ แต่ควรได้รับการกล่าวถึงอย่างครบถ้วน


ในสายตาของฉันทำงานเป็นประเภทข้อผิดพลาดโดยเจตนาเป็นรูปแบบการเข้ารหัสที่ไม่ดีเสมอ นักพัฒนาควรรู้ว่าจะคาดหวังอะไรและพร้อมที่จะจัดการกับค่านั้นโดยไม่มีข้อยกเว้น เมื่อใดก็ตามที่การดำเนินการทำในสิ่งที่มันควรจะทำ (จากจุดโปรแกรมเมอร์ของมุมมอง) และผลที่คาดว่าจะเป็นรายการหรือNoneเสมอตรวจสอบผลด้วยหรือis None is not Noneในทางกลับกันมันเป็นสไตล์ที่ไม่ดีที่จะเพิ่มข้อยกเว้นสำหรับผลลัพธ์ที่ถูกกฎหมาย ข้อยกเว้นสำหรับสิ่งที่ไม่คาดคิด ตัวอย่าง: str.find()ส่งคืน -1 หากไม่พบสิ่งใดเนื่องจากการค้นหาเสร็จสมบูรณ์โดยไม่มีข้อผิดพลาด
Bachsau

1

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


-5

โดยทั่วไปแล้วคุณไม่ควรใช้ try / catch หรือการจัดการข้อยกเว้นใด ๆ เพื่อควบคุมโฟลว์ แม้ว่าเบื้องหลังการทำซ้ำฉากจะถูกควบคุมโดยการเพิ่มStopIterationข้อยกเว้นคุณยังควรเลือกข้อมูลโค้ดแรกของคุณเป็นอันดับที่สอง


7
นี่เป็นเรื่องจริงใน Java Python รวบรวม EAFP ค่อนข้างหนัก
fengb

3
มันยังคงเป็นวิธีที่แปลก สมองของโปรแกรมเมอร์ส่วนใหญ่มีสายที่จะข้าม EAFP ในประสบการณ์ของฉัน พวกเขาสงสัยว่าทำไมบางเส้นทางจึงถูกเลือก สิ่งนี้ถูกกล่าวว่ามีเวลาและสถานที่ที่จะทำลายทุกกฎ
ilowe
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.