วิธีจัดการข้อยกเว้นในการทำความเข้าใจรายการ?


121

ฉันมีความเข้าใจรายการใน Python ซึ่งการวนซ้ำแต่ละครั้งอาจทำให้เกิดข้อยกเว้นได้

ตัวอย่างเช่นถ้าฉันมี:

eggs = (1,3,0,3,2)

[1/egg for egg in eggs]

ฉันจะได้รับZeroDivisionErrorข้อยกเว้นในองค์ประกอบที่ 3

ฉันจะจัดการกับข้อยกเว้นนี้และดำเนินการทำความเข้าใจรายการต่อไปได้อย่างไร

วิธีเดียวที่ฉันคิดได้คือใช้ฟังก์ชันตัวช่วย:

def spam(egg):
    try:
        return 1/egg
    except ZeroDivisionError:
        # handle division by zero error
        # leave empty for now
        pass

แต่มันดูยุ่งยากสำหรับฉัน

มีวิธีที่ดีกว่านี้ใน Python หรือไม่?

หมายเหตุ: นี่เป็นตัวอย่างง่ายๆ (ดู " ตัวอย่าง " ด้านบน) ที่ฉันจัดทำขึ้นเนื่องจากตัวอย่างจริงของฉันต้องการบริบทบางอย่าง ฉันไม่สนใจที่จะหลีกเลี่ยงการหารด้วยข้อผิดพลาดเป็นศูนย์ แต่ในการจัดการข้อยกเว้นในการทำความเข้าใจรายการ


4
มีPEP 463เพื่อเพิ่มนิพจน์เพื่อจัดการข้อยกเว้น [1/egg except ZeroDivisionError: None for egg in (1,3,0,3,2)]ในตัวอย่างของคุณมันจะเป็น แต่ยังอยู่ในโหมดร่าง ความรู้สึกในใจของฉันคือมันจะไม่ได้รับการยอมรับ นิพจน์อิมโฮอาจยุ่งเหยิงเกินไป (การตรวจสอบข้อยกเว้นหลาย ๆ การมีชุดค่าผสมที่ซับซ้อนมากขึ้น (ตัวดำเนินการเชิงตรรกะหลายตัวความเข้าใจที่ซับซ้อน ฯลฯ )
cfi

1
โปรดทราบว่าสำหรับการนี้โดยเฉพาะตัวอย่างเช่นคุณสามารถใช้ numpy กับการตั้งค่าที่เหมาะสมในndarray ที่จะส่งผลให้np.seterr 1/0 = nanแต่ฉันตระหนักดีว่านั่นไม่ได้กล่าวถึงสถานการณ์อื่น ๆ ที่ความต้องการนี้เกิดขึ้น
Gerrit

คำตอบ:


97

ไม่มีนิพจน์ในตัวใน Python ที่ให้คุณละเว้นข้อยกเว้น (หรือส่งคืนค่าทางเลือก & c ในกรณีที่มีข้อยกเว้น) ดังนั้นจึงเป็นไปไม่ได้ที่จะ "จัดการข้อยกเว้นในความเข้าใจรายการ" เนื่องจากความเข้าใจรายการเป็นนิพจน์ มีนิพจน์อื่นไม่มีอะไรเพิ่มเติม (กล่าวคือไม่มีข้อความและมีเพียงคำสั่งเท่านั้นที่สามารถจับ / เพิกเฉย / จัดการกับข้อยกเว้น)

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

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


14
ฉันเห็น. ดังนั้นคำตอบที่สมบูรณ์ก็คือฉันควร: 1. ใช้ฟังก์ชัน 2. ไม่ใช้ list comp understandion 3. พยายามป้องกันข้อยกเว้นแทนที่จะจัดการ
Nathan Fellman

9
ฉันไม่เห็นว่า "ไม่ใช้ความเข้าใจในรายการ" เป็นส่วนหนึ่งของคำตอบสำหรับ "วิธีจัดการข้อยกเว้นในความเข้าใจในรายการ" แต่ฉันเดาว่าคุณคงเห็นสมควรแล้วว่าเป็นผลมาจาก " ศัพท์ใน LC จึงเป็นไปไม่ได้ที่จะ จัดการข้อยกเว้น "ซึ่งเป็นส่วนแรกของคำตอบที่แท้จริง
Alex Martelli

คุณสามารถจับข้อผิดพลาดในนิพจน์ของตัวสร้างหรือความเข้าใจของตัวสร้างได้หรือไม่?

1
@AlexMartelli ข้อยกเว้นจะยากที่จะทำงานในเวอร์ชันหลามในอนาคตหรือไม่? [x[1] for x in list except IndexError pass]. ล่ามสร้างฟังก์ชันชั่วคราวเพื่อลองใช้x[1]ไม่ได้หรือ
alancalvitti

@ นาธาน 1,2,3 ข้างต้นกลายเป็นอาการปวดหัวอย่างมากในการไหลของข้อมูลที่ใช้งานได้โดยที่ 1. โดยทั่วไปต้องการให้ฟังก์ชันอินไลน์ผ่าน lambdas; 2. ทางเลือกอื่นคือการใช้จำนวนมากซ้อนกันสำหรับลูปซึ่งละเมิดกระบวนทัศน์การทำงานและนำไปสู่รหัสแนวโน้มที่ผิดพลาด 3. บ่อยครั้งที่ข้อผิดพลาดเป็นชุดข้อมูลเฉพาะกิจและชุดข้อมูลที่ซับซ้อนแฝงซึ่งเป็นคำภาษาละตินสำหรับหมายถึงข้อมูลจึงไม่สามารถป้องกันได้โดยง่าย
alancalvitti

119

ฉันรู้ว่าคำถามนี้ค่อนข้างเก่า แต่คุณสามารถสร้างฟังก์ชันทั่วไปเพื่อทำให้สิ่งนี้ง่ายขึ้น:

def catch(func, handle=lambda e : e, *args, **kwargs):
    try:
        return func(*args, **kwargs)
    except Exception as e:
        return handle(e)

จากนั้นในความเข้าใจของคุณ:

eggs = (1,3,0,3,2)
[catch(lambda : 1/egg) for egg in eggs]
[1, 0, ('integer division or modulo by zero'), 0, 0]

แน่นอนคุณสามารถสร้างฟังก์ชันแฮนเดิลเริ่มต้นตามที่คุณต้องการได้ (บอกว่าคุณต้องการคืนค่า "ไม่มี" โดยค่าเริ่มต้น)

หวังว่านี่จะช่วยคุณหรือผู้ชมคำถามนี้ในอนาคต!

หมายเหตุ: ใน python 3 ฉันจะสร้างคีย์เวิร์ดอาร์กิวเมนต์ 'handle' เท่านั้นและวางไว้ที่ท้ายรายการอาร์กิวเมนต์ สิ่งนี้จะทำให้การโต้แย้งผ่านไปอย่างแท้จริงและผ่านการจับผิดธรรมชาติมากขึ้น


2
มีประโยชน์อย่างยิ่งขอบคุณ แม้ว่าฉันจะเห็นด้วยกับความคิดเห็นทางทฤษฎี แต่สิ่งนี้แสดงให้เห็นถึงแนวทางปฏิบัติในการแก้ปัญหาที่ฉันเคยเจอซ้ำ ๆ
พอล

2
คำตอบ Greate ตัวดัดแปลงหนึ่งที่ฉันขอแนะนำคือการผ่านargsและkwargsผ่านเพื่อจัดการ ด้วยวิธีนี้คุณสามารถกลับมาพูดeggแทนฮาร์ดโค้ด0หรือยกเว้นในขณะที่คุณกำลังทำ
Mad Physicist

3
คุณอาจต้องการประเภทข้อยกเว้นเป็นอาร์กิวเมนต์ที่เป็นทางเลือก (ประเภทข้อยกเว้นสามารถเป็นพารามิเตอร์ได้หรือไม่) เพื่อให้ข้อยกเว้นที่ไม่คาดคิดถูกโยนขึ้นไปแทนที่จะละเว้นข้อยกเว้นทั้งหมด
00prometheus

3
@ ไบรอันคุณสามารถระบุโค้ดสำหรับ "ใน python 3 ได้ไหมฉันจะสร้างคีย์เวิร์ดอาร์กิวเมนต์" handle "เท่านั้นและวางไว้ที่ส่วนท้ายของรายการอาร์กิวเมนต์" พยายามวางhandleหลังจาก**kwargและได้รับSyntaxError. คุณหมายถึง dereference เป็นkwargs.get('handle',e)?
alancalvitti

21

คุณสามารถใช้ได้

[1/egg for egg in eggs if egg != 0]

สิ่งนี้จะข้ามองค์ประกอบที่เป็นศูนย์


28
นี่ไม่ได้ตอบคำถามเกี่ยวกับวิธีจัดการข้อยกเว้นในการทำความเข้าใจรายการ
Nathan Fellman

8
อืมใช่แล้ว มันขัดขวางความจำเป็นในการจัดการกับข้อยกเว้น ใช่มันไม่ใช่วิธีแก้ปัญหาที่ถูกต้องตลอดเวลา แต่เป็นวิธีที่ธรรมดา
Peter

3
ฉันเข้าใจ. ฉันรับความคิดเห็นกลับมา (แม้ว่าฉันจะไม่ลบมันเนื่องจาก 'การสนทนา' สั้น ๆ นั้นปรับปรุงคำตอบ)
Nathan Fellman

11

ไม่มีไม่มีวิธีที่ดีกว่านี้ ในหลาย ๆ กรณีคุณสามารถใช้การหลีกเลี่ยงได้เช่นเดียวกับปีเตอร์

ทางเลือกอื่นของคุณคืออย่าใช้ความเข้าใจ

eggs = (1,3,0,3,2)

result=[]
for egg in eggs:
    try:
        result.append(egg/0)
    except ZeroDivisionError:
        # handle division by zero error
        # leave empty for now
        pass

ขึ้นอยู่กับคุณที่จะตัดสินใจว่าจะยุ่งยากกว่านี้หรือไม่


1
ฉันจะใช้ความเข้าใจที่นี่ได้อย่างไร
Nathan Fellman

@ นาธาน: คุณคงไม่ gnibbler พูดว่า: ไม่มีไม่มีวิธีที่ดีกว่านี้
SilentGhost

ขออภัย ... ฉันพลาด 'ไม่' ในคำตอบของเขา :-)
Nathan Fellman

4

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

# A modified version of the helper function by the Question starter 
def spam(egg):
    try:
        return 1/egg, None
    except ZeroDivisionError as err:
        # handle division by zero error        
        return None, err

[(1/1, None), (1/3, None), (None, ZeroDivisionError), (1/3, None), (1/2, None)]การส่งออกจะเป็นแบบนี้ ด้วยคำตอบนี้คุณสามารถควบคุมได้อย่างเต็มที่เพื่อดำเนินการต่อในแบบที่คุณต้องการ


ดี. สิ่งนี้มีลักษณะคล้ายกันมากกับEitherประเภทในภาษาโปรแกรมที่ใช้งานได้ (เช่น Scala) โดยที่Eitherสามารถมีค่าประเภทหนึ่งหรืออีกประเภทหนึ่ง แต่ไม่ใช่ทั้งสองอย่าง ข้อแตกต่างเพียงอย่างเดียวคือในภาษาเหล่านั้นสำนวนที่จะใส่ข้อผิดพลาดทางด้านซ้ายและค่าทางด้านขวา นี่คือบทความที่มีข้อมูลเพิ่มเติม
Alex Palmer

3

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

eggs = (1,3,0,3,2)
[1/egg if egg > 0 else None for egg in eggs]


Output: [1, 0, None, 0, 0]

ไม่เหมือนกับคำตอบนี้หรือ? stackoverflow.com/a/1528244/1084
Nathan Fellman

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