เหตุใดการขอให้อภัยจึงง่ายกว่าการขออนุญาตใน Python

เหตุใด "การขอการให้อภัยจึงง่ายกว่าการขออนุญาต" (EAFP) ถือเป็นแนวปฏิบัติที่ดีใน Python หรือไม่ ในฐานะมือใหม่ด้านการเขียนโปรแกรม ฉันรู้สึกว่าการใช้รูทีน try...except หลายๆ รูทีนจะทำให้โค้ดบวมและอ่านได้น้อยกว่าเมื่อเทียบกับการใช้เช็คอื่นๆ

ข้อดีของแนวทาง EAFP คืออะไร?

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


person n1000    schedule 02.10.2015    source แหล่งที่มา
comment
การยืนยันไม่ได้มีไว้สำหรับการไหลของโค้ด   -  person Ignacio Vazquez-Abrams    schedule 02.10.2015
comment
@ IgnacioVazquez-Abrams คุณช่วยอธิบายรายละเอียดหน่อยได้ไหม? ในความเข้าใจของฉัน พวกเขาเป็นทางเลือกแทน try...except? ฉันควรเปลี่ยนคำถามไหม?   -  person n1000    schedule 02.10.2015
comment
ควรใช้เพื่อให้แน่ใจว่าโปรแกรมของคุณไม่ได้ทำอะไรโง่ๆ ในสถานการณ์ที่เป็นรูปลูกแพร์ ไม่ใช่เพื่อตรวจสอบว่ามีใครบางคนส่งผ่านจำนวนเต็มแทนที่จะเป็นสตริง   -  person Ignacio Vazquez-Abrams    schedule 02.10.2015
comment
ตกลง - ฉันลบ assert แล้ว เห็นได้ชัดว่าฉันไม่ได้ใช้สิ่งเหล่านั้นอย่างถูกต้องในอดีต ...   -  person n1000    schedule 02.10.2015


คำตอบ (4)


LBYL ซึ่งเป็นแนวทางตอบโต้กับ EAFP ไม่มีส่วนเกี่ยวข้องกับการยืนยัน เพียงหมายความว่าคุณต้องเพิ่มเครื่องหมายถูกก่อนที่จะลอง เพื่อเข้าถึงบางสิ่งที่อาจไม่มีอยู่ที่นั่น

เหตุผลที่ Python เป็น EAFP นั้นต่างจากภาษาอื่นๆ (เช่น Java) - ใน Python การจับข้อยกเว้นนั้นมีราคาไม่แพงนัก และนั่นคือเหตุผลว่าทำไมคุณถึงได้รับการสนับสนุนให้ใช้มัน

ตัวอย่างสำหรับ EAFP:

try:
    snake = zoo['snake']
except KeyError as e:
    print "There's no snake in the zoo"
    snake = None

ตัวอย่างสำหรับ LBYL:

if 'snake' in zoo:
    snake = zoo['snake']
else:
    snake = None
person Nir Alfasi    schedule 02.10.2015
comment
โปรดทราบว่าอันที่สองควรใช้ออบเจ็กต์ Sentinel ที่ไม่ซ้ำใคร มิฉะนั้นจะล้มเหลวด้วยค่าเท็จ - person Ignacio Vazquez-Abrams; 02.10.2015
comment
@ IgnacioVazquez-Abrams คุณพูดถูก แต่ในตัวอย่างนี้ ฉันคิดว่าฉันสามารถสรุปได้อย่างปลอดภัยว่า False, 0 หรือค่าเท็จอื่น ๆ จะไม่เป็นตัวแทนของงูในสวนสัตว์ :) - person Nir Alfasi; 02.10.2015
comment
หากคุณใช้ dict.get คุณควรส่ง None เป็นค่าเริ่มต้น: zoo.get('snake', None) นี่ไม่ใช่ตัวอย่างที่ดีสำหรับ LBYL เนื่องจากการใช้การให้อภัย dict.get - person poke; 02.10.2015
comment
@poke true สามารถย่อให้สั้นลงได้: snake = zoo.get('snake', None) แต่ประเด็นคือเพื่อแสดงตัวอย่างของ LBYL ไม่ใช่วิธีการแก้ไข :) - person Nir Alfasi; 02.10.2015
comment
จากนั้นทำส่วน "มอง" ให้ถูกต้องและตรวจสอบว่ามีคีย์อยู่ในพจนานุกรมหรือไม่โดยไม่ได้รับค่าจากคีย์อย่างอภัย และตรวจสอบความจริงของค่านั้น - person poke; 02.10.2015
comment
เหตุผลที่ Python เป็น EAFP นั้นไม่เหมือนกับภาษาอื่น ๆ (เช่น Java) - ใน Python การจับข้อยกเว้นนั้นเป็นการดำเนินการที่ไม่แพง : นั่นมันผิด การตั้งค่าบล็อก try/ยกเว้น มีราคาถูก แต่การตรวจจับข้อยกเว้นไม่ได้เป็นเช่นนั้น ลองใช้โค้ดนี้เพื่อตรวจสอบ gist.github.com/RealBigB/38f4579c543261f1400f - ที่นี่ (python 2.7.x) ฉันได้รับ 1.3156080246 สำหรับ eafp และ 0.368113994598 สำหรับ lbyl - person bruno desthuilliers; 02.10.2015
comment
@brunodeshuilliers คุณพิจารณาตัวอย่างที่มีข้อยกเว้นนับล้านรายการและจับได้ว่าเป็นตัวอย่างที่ดีที่สามารถใช้เป็นแบบจำลองสำหรับโปรแกรมปกติได้หรือไม่? :) - person Nir Alfasi; 02.10.2015
comment
@brunodeshuilliers ในขณะที่ฉันพยายามอธิบายในคำตอบของฉัน การจับข้อยกเว้นใน Python นั้นถูกกว่าเมื่อเปรียบเทียบกับภาษาอื่น แต่แน่นอนว่ามันไม่ฟรี ข้อยกเว้นยังคงมีอยู่สำหรับกรณีพิเศษ ในกรณีนี้ หากคุณคาดว่าคีย์จะมีอยู่เกือบตลอดเวลา จากนั้นการตรวจสอบเพิ่มเติมสำหรับ LBYL จะมีราคาแพงกว่าเล็กน้อย - person poke; 02.10.2015
comment
@brunodeshuilliers ใช้ตัวอย่างของคุณ: (1.3156080246-0.368113994598)/1000000 = 0.00000094749403 ซึ่งเป็นต้นทุนเฉลี่ยสำหรับการตรวจจับข้อยกเว้นหนึ่งรายการในตัวอย่างที่คุณให้ไว้ นั่นน้อยกว่าหนึ่งไมโครวินาทีหรือ 947 นาโนวินาทีถ้าให้แม่นยำกว่านี้... - person Nir Alfasi; 02.10.2015
comment
@alfasin wrt/ รหัสมันเป็นเพียงตัวอย่างของคุณเอง - และการระบุว่าใน Python การจับข้อยกเว้นนั้นเป็นการดำเนินการที่ไม่แพงถือเป็นการทำให้เข้าใจผิด มันอาจจะถูกกว่าในภาษาอื่นบางภาษา แต่ก็ยังแพงกว่าการค้นหาคีย์อยู่มาก - person bruno desthuilliers; 02.10.2015
comment
@brunodeshuilliers คุณนำคำพูดของฉันออกจากบริบท มันมีราคาไม่แพงเมื่อเปรียบเทียบกับภาษาอื่น และดังนั้นจึงสนับสนุนให้ใช้ ฉันไม่ได้บอกว่ามันเป็น 'ฟรี' - person Nir Alfasi; 02.10.2015
comment
@brunodeshuilliers แต่มัน เป็น การดำเนินการที่ไม่แพง นั่นไม่ได้หมายความว่าคุณสามารถเปรียบเทียบกับสิ่งอื่นที่ถูกกว่าและบอกว่ามันผิดได้ มันเหมือนกับการบอกว่า print มีราคาแพง เพราะการไม่พิมพ์จะถูกกว่ามาก ลองเรียกใช้ตัวอย่างเดียวกันในเช่น Java และคุณสามารถดูว่าทำไมข้อยกเว้นจึงมีราคาไม่แพงที่นี่ - person poke; 02.10.2015
comment
ครั้งหนึ่งฉันต้องแก้ไขโค้ดของคนอื่นสำหรับงานคอมพิวเตอร์ที่ค่อนข้างซับซ้อนในแต่ละวันซึ่งใช้เวลานานเกินไป เขาใช้ EAFP ไปทั่วสถานที่ เพียงแค่เปลี่ยนสิ่งนี้เป็น LBYL เวลาในการดำเนินการก็เกือบครึ่งหนึ่ง ในตัวอย่างข้างต้น เวอร์ชัน EAFP จะช้ากว่าเพียง 3.5 เท่าเท่านั้น แน่นอนว่ามีราคาไม่แพง และนั่นจะเป็นความคิดเห็นสุดท้ายของฉันเกี่ยวกับเรื่องนี้ - person bruno desthuilliers; 02.10.2015
comment
@brunodeshuilliers ที่ช้าลง 3.5 เท่านี้ ทำให้ช้าลง 1 วินาทีต่อการดำเนินการ ล้าน ครั้ง เมื่อคุณปรับปรุงโค้ดของเขา คุณต้องได้ทำสิ่งอื่น ๆ ที่คุณไม่รู้ด้วยซ้ำ... - person Nir Alfasi; 02.10.2015

คุณกำลังผสมสองสิ่งที่นี่: การยืนยันและตรรกะที่ใช้ EAFP

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

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

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

# LBYL
if key in dic:
    print(dic[key])
else:
    handleError()

# EAFP
try:
    print(dic[key])
except KeyError:
    handleError()

ตอนนี้สิ่งนี้ดูคล้ายกันมาก แม้ว่าคุณควรจำไว้ว่าโซลูชัน LBYL จะตรวจสอบพจนานุกรม สองครั้ง เช่นเดียวกับโค้ดทั้งหมดที่ตรวจจับข้อยกเว้น คุณควรดำเนินการเฉพาะในกรณีที่การไม่มีคีย์นั้นเป็น กรณีพิเศษ ดังนั้นหากโดยปกติแล้ว รหัสที่ให้มาจะถูกยกเว้นให้อยู่ในพจนานุกรม แสดงว่าเป็น EAFP และคุณควรเข้าถึงโดยตรง หากคุณไม่ได้คาดหวังให้คีย์ปรากฏในพจนานุกรม คุณควรตรวจสอบการมีอยู่ของมันก่อน (แม้ว่าข้อยกเว้นจะมีราคาถูกกว่าใน Python แต่ก็ยังไม่ฟรี ดังนั้นควรเก็บไว้เป็นกรณีพิเศษ)

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

person poke    schedule 02.10.2015
comment
ขอบคุณ Big +1 สำหรับการแสดงและอธิบายข้อดีต่างๆ ของแนวทาง EAFP ใน Python - person n1000; 02.10.2015

คำถามที่ดี! มีคำถามน้อยมากใน StackOverflow ที่ถามเกี่ยวกับ "ปรัชญาเบื้องหลังหลักการ"

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

# LBYL
if key in dic:
    print(dic[key])
else:
    handleError()

# EAFP
try:
    print(dic[key])
except KeyError:
    handleError()

ฉันจะบอกว่าช่วงเวลาที่แท้จริงที่ EAFP โดดเด่นก็คือคุณไม่ได้เขียน try ... except ... เลย อย่างน้อยก็ไม่ใช่ในฐานโค้ดพื้นฐานส่วนใหญ่ของคุณ เพราะ กฎข้อแรกของการจัดการข้อยกเว้น: อย่าทำ การจัดการข้อยกเว้น ด้วยเหตุนี้ เรามาเขียนตัวอย่างที่ 2 ใหม่กัน:

# Real EAFP
print(dic[key])

ตอนนี้แนวทาง EAFP ที่แท้จริงนั้นไม่สะอาดและรวดเร็วใช่ไหม

person RayLuo    schedule 23.04.2019

ฉันจะขยายคำตอบจาก @RayLuo

ปัญหาของ LBYL คือ จริงๆ แล้วมันไม่ได้ผลโดยทั่วไป เว้นแต่ว่าคุณมีแอปพลิเคชันแบบเธรดเดียว ก็จะมีสภาวะการแข่งขันที่เป็นไปได้เสมอ:

# LBYL
if key in dic:
    # RACE CONDITION
    print(dic[key])
else:
    handleError()

# EAFP
try:
    print(dic[key])
except KeyError:
    handleError()

คุณสามารถเพิ่มคีย์ลงใน dict ระหว่างการตรวจสอบ if และ และ print นั่นฟังดูไม่น่าจะเป็นไปได้อย่างไม่น่าเชื่อในกรณีนี้โดยเฉพาะ อย่างไรก็ตาม มีความเป็นไปได้มากกว่ามากหากการตรวจสอบที่คุณทำนั้นขัดแย้งกับ DB, API หรือแหล่งข้อมูลภายนอกใดๆ ที่สามารถเปลี่ยนแปลงแบบอะซิงโครนัสกับข้อมูลแอปพลิเคชันของคุณ

นั่นหมายถึงวิธีที่ถูกต้องในการใช้ LBYL คือ:

if key in dic:
    try:
        print(dic[key])
    except KeyError:
        handleError()
else:
    handleError()

โปรดทราบว่าส่วนคำสั่ง try / except นั้นเหมือนกับวิธี EAFP ทุกประการ

เนื่องจากคุณต้องจัดการข้อยกเว้นในลักษณะ EAFP แม้ว่าจะใช้วิธีการ LBYL คุณจึงอาจใช้แนวทาง EAFP ในตอนแรกได้เช่นกัน

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

บรรทัดล่าง: LBYL ไม่ทำงานในกรณีทั่วไป แต่ EAFP ใช้งานได้ นักพัฒนาที่ดีมุ่งเน้นไปที่รูปแบบการแก้ปัญหาทั่วไปที่พวกเขาสามารถใช้ได้อย่างมั่นใจกับปัญหาต่างๆ มากมาย เรียนรู้การใช้ EAFP อย่างสม่ำเสมอ

person Chris Johnson    schedule 01.03.2021