क्या पायथन में प्रवाह नियंत्रण के सर्वोत्तम अपवाद हैं?


15

मैं "लर्निंग पाइथन" पढ़ रहा हूं और निम्नलिखित में आया हूं:

उपयोगकर्ता-परिभाषित अपवाद भी गैर-शर्तों का संकेत दे सकते हैं। उदाहरण के लिए, एक खोज रूटीन को एक अपवाद जुटाने के लिए कोडित किया जा सकता है जब कॉल करने वाले की व्याख्या करने के लिए एक स्टेटस फ्लैग वापस करने के बजाय एक मैच पाया जाता है। निम्नलिखित में, प्रयास / छोड़कर / अन्यथा अपवाद हैंडलर एक / / अन्य रिटर्न-वैल्यू परीक्षक का काम करता है:

class Found(Exception): pass

def searcher():
    if ...success...:
        raise Found()            # Raise exceptions instead of returning flags
    else:
        return

क्योंकि पायथन कोर के लिए गतिशील रूप से टाइप और बहुरूपी है, प्रहरी रिटर्न वैल्यू के बजाय अपवाद, ऐसी परिस्थितियों को संकेत करने के लिए आमतौर पर पसंदीदा तरीका है।

मैंने देखा है कि इस तरह की बात कई बार विभिन्न मंचों पर चर्चा की गई है, और छोरों को समाप्त करने के लिए स्टॉपइंटरनेशन का उपयोग करते हुए पायथन के संदर्भ हैं, लेकिन मैं आधिकारिक शैली गाइड में बहुत कुछ नहीं पा सकता हूं (प्रवाह नियंत्रण के लिए अपवादों के लिए पीईपी 8 में एक आधिकारिक संदर्भ है) डेवलपर्स से या बयान। क्या ऐसा कुछ आधिकारिक है जो बताता है कि यह पायथन के लिए सबसे अच्छा अभ्यास है?

यह ( क्या नियंत्रण प्रवाह के रूप में अपवाद एक गंभीर एंटीपर्टर्न माना जाता है? यदि हां, तो क्यों? ) भी कई टिप्पणीकारों ने कहा है कि यह शैली पाइथोनिक है। यह किस पर आधारित है?

TIA

जवाबों:


26

आम सहमति "अपवादों का उपयोग नहीं करती!" ज्यादातर अन्य भाषाओं से आता है और यहां तक ​​कि कभी-कभी पुराना हो जाता है।

  • C ++ में, "स्टैक अनइंडिंग" के कारण एक अपवाद को फेंकना बहुत महंगा है । प्रत्येक स्थानीय चर घोषणा withपायथन में एक बयान की तरह है , और उस चर में वस्तु विनाशकों को चला सकती है। ये विध्वंसक निष्पादित किए जाते हैं जब एक अपवाद फेंक दिया जाता है, लेकिन एक समारोह से लौटते समय भी। यह "RAII मुहावरा" एक अभिन्न भाषा की विशेषता है और मजबूत, सही कोड लिखने के लिए सुपर महत्वपूर्ण है - इसलिए RAII बनाम सस्ते अपवाद एक ट्रेडऑफ़ था जिसे C ++ ने RAII के लिए तय किया।

  • प्रारंभिक C ++ में, बहुत सारे कोड अपवाद-सुरक्षित तरीके से नहीं लिखे गए थे: जब तक आप वास्तव में RAII का उपयोग नहीं करते हैं, स्मृति और अन्य संसाधनों को लीक करना आसान है। इसलिए अपवादों को फेंकने से वह कोड गलत हो जाएगा। C ++ मानक लाइब्रेरी अपवादों का उपयोग करने के बाद भी यह उचित नहीं है: आप अपवाद नहीं दिखा सकते हैं जो मौजूद नहीं हैं। हालाँकि, C ++ के साथ C कोड को जोड़ते समय अपवाद अभी भी एक समस्या है।

  • जावा में, हर अपवाद में एक संबद्ध स्टैक ट्रेस होता है। स्टैक ट्रेस बहुत मूल्यवान है जब त्रुटियों को डीबग करना होता है, लेकिन व्यर्थ प्रयास तब होता है जब अपवाद कभी मुद्रित नहीं किया जाता है, उदाहरण के लिए क्योंकि यह केवल नियंत्रण प्रवाह के लिए उपयोग किया गया था।

इसलिए उन भाषाओं में अपवाद "बहुत महंगे हैं" जिनका उपयोग नियंत्रण प्रवाह के रूप में किया जाना है। पायथन में यह एक मुद्दे से कम है और अपवाद बहुत सस्ते हैं। इसके अतिरिक्त, पायथन भाषा पहले से ही कुछ ओवरहेड से ग्रस्त है, जो अन्य नियंत्रण प्रवाह निर्माणों की तुलना में अपवादों की लागत को ध्यान नहीं देता है: उदाहरण के लिए, यदि एक स्पष्ट सदस्यता परीक्षण के साथ एक तानाशाही प्रविष्टि मौजूद if key in the_dict: ...है तो आम तौर पर बस प्रवेश the_dict[key]; ...और जाँच की पहुँच से ठीक तेज़ है यदि आप एक KeyError प्राप्त करें। कुछ अभिन्न भाषा सुविधाएँ (जैसे जनरेटर) अपवादों के संदर्भ में डिज़ाइन की गई हैं।

इसलिए जबकि पायथन में अपवादों से विशेष रूप से बचने का कोई तकनीकी कारण नहीं है, फिर भी यह सवाल है कि क्या आपको वापसी मूल्यों के बजाय उनका उपयोग करना चाहिए। अपवादों के साथ डिज़ाइन-स्तरीय समस्याएं हैं:

  • वे बिल्कुल स्पष्ट नहीं हैं। आप आसानी से एक फ़ंक्शन को नहीं देख सकते हैं और देख सकते हैं कि यह किस अपवाद को फेंक सकता है, इसलिए आप हमेशा नहीं जानते कि क्या पकड़ना है। वापसी मूल्य अधिक अच्छी तरह से परिभाषित हो जाता है।

  • अपवाद गैर-स्थानीय नियंत्रण प्रवाह है जो आपके कोड को जटिल बनाता है। जब आप एक अपवाद फेंकते हैं, तो आपको पता नहीं होता है कि नियंत्रण प्रवाह फिर से कहाँ शुरू होगा। उन त्रुटियों के लिए जिन्हें तुरंत नियंत्रित नहीं किया जा सकता है, यह संभवतः एक अच्छा विचार है, जब किसी शर्त के अपने कॉलर को सूचित करना यह पूरी तरह अनावश्यक है।

पायथन संस्कृति आम तौर पर अपवादों के पक्ष में है, लेकिन ओवरबोर्ड पर जाना आसान है। एक list_contains(the_list, item)फ़ंक्शन की कल्पना करें जो यह जांचता है कि सूची में उस आइटम के बराबर आइटम है या नहीं। यदि परिणाम अपवादों के माध्यम से संप्रेषित होता है जो बिल्कुल कष्टप्रद है, क्योंकि हमें इसे इस तरह से कॉल करना होगा:

try:
  list_contains(invited_guests, person_at_door)
except Found:
  print("Oh, hello {}!".format(person_at_door))
except NotFound:
  print("Who are you?")

एक बूल लौटना ज्यादा स्पष्ट होगा:

if list_contains(invited_guests, person_at_door):
  print("Oh, hello {}!".format(person_at_door))
else:
  print("Who are you?")

यदि फ़ंक्शन पहले से ही मान लौटाने वाला है, तो विशेष शर्तों के लिए एक विशेष मान वापस करना त्रुटि-प्रवण है, क्योंकि लोग इस मान की जांच करना भूल जाएंगे (यह संभवतः C में समस्याओं का 1/3 कारण है)। एक अपवाद आमतौर पर अधिक सही होता है।

एक अच्छा उदाहरण एक pos = find_string(haystack, needle)फ़ंक्शन है जो needle`हैस्टैक स्ट्रिंग में स्ट्रिंग की पहली घटना के लिए खोज करता है , और प्रारंभ स्थिति देता है। लेकिन क्या होगा अगर वे हिस्टैक-स्ट्रिंग में सुई-स्ट्रिंग शामिल नहीं करते हैं?

सी द्वारा हल और पायथन द्वारा नकल एक विशेष मूल्य वापस करना है। सी में यह एक शून्य सूचक है, पायथन में यह है -1। यह आश्चर्यजनक परिणाम देगा जब स्थिति को जाँच के बिना एक स्ट्रिंग सूचकांक के रूप में उपयोग किया जाता है, विशेष रूप -1से पायथन में एक वैध सूचकांक के रूप में। C में, आपका NULL पॉइंटर कम से कम आपको segfault देगा।

PHP में, एक अलग प्रकार का एक विशेष मान लौटाया जाता है: FALSEपूर्णांक के बजाय बूलियन । जैसा कि यह पता चलता है कि यह वास्तव में भाषा के अंतर्निहित रूपांतरण नियमों के कारण बेहतर नहीं है (लेकिन ध्यान दें कि पायथन में बूलियन को भी चींटियों के रूप में इस्तेमाल किया जा सकता है!)। लगातार प्रकार नहीं लौटाने वाले कार्यों को आम तौर पर बहुत भ्रमित माना जाता है।

एक अधिक मजबूत संस्करण एक अपवाद फेंकने के लिए होता है जब स्ट्रिंग नहीं पाया जा सकता है, जो यह सुनिश्चित करता है कि सामान्य नियंत्रण प्रवाह के दौरान गलती से एक साधारण मूल्य के स्थान पर विशेष मूल्य का उपयोग करना असंभव है:

 try:
   pos = find_string(haystack, needle)
   do_something_with(pos)
 except NotFound:
   ...

वैकल्पिक रूप से, हमेशा एक प्रकार का रिटर्न जो सीधे इस्तेमाल नहीं किया जा सकता है, लेकिन पहले इसे अनफ्रीप्ड किया जाना चाहिए, उदाहरण के लिए इस्तेमाल किया जा सकता है, उदाहरण के लिए बूल टुपल जहां बूलियन इंगित करता है कि क्या अपवाद हुआ या यदि परिणाम उपयोग करने योग्य है। फिर:

pos, ok = find_string(haystack, needle)
if not ok:
  ...
do_something_with(pos)

यह आपको समस्याओं को तुरंत संभालने के लिए मजबूर करता है, लेकिन यह बहुत जल्दी परेशान हो जाता है। यह आपको आसानी से कार्य करने से रोकता है। प्रत्येक फ़ंक्शन कॉल को अब कोड की तीन पंक्तियों की आवश्यकता होती है। गोलंग एक ऐसी भाषा है जो सोचती है कि यह उपद्रव सुरक्षा के लायक है।

इसलिए संक्षेप में, अपवाद पूरी तरह से समस्याओं के बिना नहीं हैं और निश्चित रूप से इसका अत्यधिक उपयोग किया जा सकता है, खासकर जब वे "सामान्य" वापसी मूल्य को प्रतिस्थापित करते हैं। लेकिन जब विशेष परिस्थितियों का संकेत देने के लिए उपयोग किया जाता है (जरूरी नहीं कि सिर्फ त्रुटियां), तो अपवाद आपको एपीआई विकसित करने में मदद कर सकते हैं जो साफ, सहज, उपयोग करने में आसान और दुरुपयोग करने में मुश्किल हैं।


1
में गहराई से जवाब के लिए धन्यवाद। मैं जावा जैसी अन्य भाषाओं की पृष्ठभूमि से आ रहा हूं, जहां "अपवाद केवल असाधारण स्थितियों के लिए हैं," इसलिए यह मेरे लिए नया है। क्या कोई पायथन कोर देवता हैं जिन्होंने कभी इस तरह से कहा है कि उनके पास अन्य पायथन दिशानिर्देश जैसे ईएएफपी, ईआईबीटीआई, आदि हैं (मेलिंग सूची, ब्लॉग पोस्ट आदि पर) मैं कुछ आधिकारिक का हवाला देना चाहूंगा यदि एक और देव / बॉस पूछता है। धन्यवाद!
जेबी

अपने कस्टम अपवाद वर्ग के fillInStackTrace विधि को ओवरलोड करके, बस तुरंत वापस करने के लिए, आप इसे उठाने के लिए बहुत सस्ता बना सकते हैं, क्योंकि यह स्टैक-वॉकिंग महंगा है और यह वह जगह है जहाँ यह किया जाता है।
जॉन कोवन

आपके इंट्रो में आपकी पहली गोली पहली बार में भ्रमित करने वाली थी। शायद आप इसे कुछ बदल सकते हैं जैसे "आधुनिक C ++ में अपवाद हैंडलिंग कोड शून्य-लागत है, लेकिन एक अपवाद फेंकना महंगा है"? (लंकर्स के लिए: stackoverflow.com/questions/13835817/… ) [संपादित करें: संपादित प्रस्तावित करें]
phresnel

ध्यान दें कि डिक्शनरी लुकअप ऑपरेशन्स के लिए collections.defaultdictया my_dict.get(key, default)कोड का उपयोग करने की तुलना में बहुत अधिक स्पष्ट हैtry: my_dict[key] except: return default
स्टीव बार्न्स

11

नहीं! - सामान्य तौर पर नहीं - एकल वर्ग के अपवाद के साथ अपवादों को अच्छा प्रवाह नियंत्रण अभ्यास नहीं माना जाता है। एक जगह जहां अपवाद को एक उचित, या उससे भी बेहतर माना जाता है, किसी स्थिति को इंगित करने का तरीका जनरेटर या पुनरावृत्त संचालन है। ये ऑपरेशन एक वैध परिणाम के रूप में किसी भी संभावित मूल्य को वापस कर सकते हैं इसलिए एक खत्म करने के लिए एक तंत्र की आवश्यकता होती है।

एक बार में एक बाइट स्ट्रीम की एक बाइनरी फ़ाइल को पढ़ने पर विचार करें - बिल्कुल कोई भी मूल्य संभावित रूप से मान्य परिणाम है लेकिन हमें अभी भी फ़ाइल के अंत का संकेत देना होगा। तो हमारे पास एक विकल्प है, दो मान लौटाएँ, (बाइट मान और एक वैध ध्वज), हर बार या जब कोई और अधिक करने के लिए कोई अपवाद न हो तो एक अपवाद बढ़ाएं। दो मामलों में खपत कोड निम्न प्रकार दिख सकता है:

# Using validity flag
valid, val = readbyte(source)
while valid:
    processbyte(val)
    valid, val = readbyte(source)
tidy_up()

वैकल्पिक रूप से:

# With exceptions
try:
   val = readbyte(source)
   processbyte(val) # Note if a problem occurs here it will also raise an exception
except Exception: # Use a specific exception here!
   tidy_up()

लेकिन यह तब से है, जब पीईपी 343 को लागू किया गया था और वापस पोर्ट किया गया था, सभी बड़े करीने से withबयान में लिपटे हुए थे । ऊपर बन जाता है, बहुत ही अजगर:

with open(source) as input:
    for val in input.readbyte(): # This line will raise a StopIteration exception an call input.__exit__()
        processbyte(val) # Not called if there is nothing read

अजगर 3 में यह बन गया:

for val in open(source, 'rb').read():
     processbyte(val)

मैं आपको पीईपी 343 को पढ़ने का आग्रह करता हूं जो पृष्ठभूमि, औचित्य, उदाहरण आदि देता है।

प्रसंस्करण के अंत का संकेत देने के लिए एक अपवाद का उपयोग करना भी सामान्य है जब फिनिश को संकेत देने के लिए जनरेटर कार्यों का उपयोग किया जाता है।

मैं जोड़ना चाहूंगा कि आपका खोजकर्ता उदाहरण लगभग निश्चित रूप से पीछे की ओर है, इस तरह के फ़ंक्शन जनरेटर होने चाहिए, पहले कॉल पर पहला मैच लौटाएं, फिर अगले मैच में कॉल करने वाले सबस्टीट्यूट कॉल और अधिक मैच NotFoundनहीं होने पर एक अपवाद को बढ़ाएं।


आप कहते हैं: "नहीं! - सामान्य रूप से नहीं - अपवादों को कोड के एकल वर्ग के अपवाद के साथ अच्छा प्रवाह नियंत्रण अभ्यास नहीं माना जाता है"। इस जोरदार बयान के लिए तर्क कहां है? यह उत्तर पुनरावृत्ति मामले पर ध्यान केंद्रित करने के लिए लगता है। कई अन्य मामले हैं जहां लोग अपवादों का उपयोग करने के बारे में सोच सकते हैं। उन अन्य मामलों में ऐसा करने में क्या समस्या है?
डैनियल वाल्ट्रिप

@DanielWaltrip मैं बहुत अधिक कोड देखता हूं, विभिन्न प्रोग्रामिंग भाषाओं में, जहां अपवाद का उपयोग किया जाता है, अक्सर बहुत व्यापक रूप से, जब एक सरल ifबेहतर होगा। जब यह डीबगिंग और परीक्षण या आपके कोड व्यवहार के बारे में तर्क देने की बात आती है, तो अपवादों पर भरोसा नहीं करना बेहतर होता है - उन्हें अपवाद होना चाहिए। मैंने देखा है, उत्पादन कोड में, एक गणना जो एक साधारण रिटर्न के बजाय थ्रो / कैच के माध्यम से लौटती है, इसका मतलब यह है कि जब गणना शून्य त्रुटि से विभाजित होती है तो यह दुर्घटनाग्रस्त होने के बजाय एक यादृच्छिक मान लौटाती है जहां त्रुटि कई घंटे तक होती है डिबगिंग।
स्टीव बार्न्स

अरे @SteveBarnes, जीरो एरर कैच उदाहरण से विभक्त खराब प्रोग्रामिंग की तरह लगता है (बहुत सामान्य कैच के साथ जो अपवाद को फिर से नहीं बढ़ाता)।
wTyeRogers 16

आपके उत्तर से लगता है: A) "NO!" बी) ऐसे वैध उदाहरण हैं जहां अपवादों के माध्यम से नियंत्रण प्रवाह वैकल्पिक विकल्पों की तुलना में बेहतर काम करता है) जनरेटर का उपयोग करने के लिए प्रश्न के कोड का एक सुधार (जो कि नियंत्रण प्रवाह के रूप में अपवादों का उपयोग करते हैं, और ऐसा अच्छी तरह से करते हैं)। हालांकि आपका जवाब आम तौर पर जानकारीपूर्ण होता है, आइटम ए का समर्थन करने के लिए इसमें कोई तर्क मौजूद नहीं है, जो कि बोल्ड और प्रतीत होता है कि प्राथमिक बयान में, मैं कुछ समर्थन की उम्मीद करूंगा। यदि इसका समर्थन किया गया था, तो मैं आपके उत्तर को अस्वीकार नहीं करूंगा। काश ...
wTyeRogers 16
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.