मुझे अपवादों का उपयोग कब और कैसे करना चाहिए?


20

सेटिंग

मुझे अक्सर यह निर्धारित करने में परेशानी होती है कि अपवाद का उपयोग कब और कैसे करना है। आइए एक सरल उदाहरण पर विचार करें: मान लें कि मैं एक वेबपेज को स्क्रैप कर रहा हूं, " http://www.abevigoda.com/ ", यह निर्धारित करने के लिए कि अबे विगोडा अभी भी जीवित है। ऐसा करने के लिए, हमें बस इतना करना होगा कि पेज डाउनलोड करें और बार देखें कि "अबे विगोडा" वाक्यांश दिखाई देता है। हम पहली उपस्थिति लौटाते हैं, क्योंकि इसमें अबे की स्थिति शामिल है। वैचारिक रूप से, यह इस तरह दिखेगा:

def get_abe_status(url):
    # download the page
    page = download_page(url)

    # get all mentions of Abe Vigoda
    hits = page.find_all_mentions("Abe Vigoda")

    # parse the first hit for his status
    status = parse_abe_status(hits[0])

    # he's either alive or dead
    return status == "alive"

जहां parse_abe_status(s)"अबे विगोडा कुछ है " के रूप में एक स्ट्रिंग लेता है और " कुछ " भाग वापस करता है ।

इससे पहले कि आप तर्क दें कि अबे की स्थिति के लिए इस पृष्ठ को स्क्रैप करने के बहुत बेहतर और अधिक मजबूत तरीके हैं, याद रखें कि यह एक सामान्य स्थिति को उजागर करने के लिए उपयोग किया जाने वाला एक सरल और वंचित उदाहरण है।

अब, यह कोड समस्याओं का सामना कहाँ कर सकता है? अन्य त्रुटियों में, कुछ "अपेक्षित" हैं:

  • download_pageपृष्ठ को डाउनलोड करने में सक्षम नहीं हो सकता है, और एक फेंकता है IOError
  • URL सही पृष्ठ पर इंगित नहीं हो सकता है, या पृष्ठ गलत तरीके से डाउनलोड किया गया है, और इसलिए कोई हिट नहीं है। hitsखाली सूची है, फिर।
  • वेब पेज को बदल दिया गया है, संभवतः पेज के बारे में हमारी धारणा गलत है। शायद हम अबे विगोडा के 4 उल्लेखों की उम्मीद करते हैं, लेकिन अब हम 5 पाते हैं।
  • कुछ कारणों से, hits[0]"अबे विगोडा कुछ है " फॉर्म का एक तार नहीं हो सकता है , और इसलिए इसे सही ढंग से पार्स नहीं किया जा सकता है।

पहला मामला वास्तव में मेरे लिए कोई समस्या नहीं है: एक IOErrorफेंक दिया गया है और मेरे फ़ंक्शन के कॉलर द्वारा नियंत्रित किया जा सकता है। तो आइए अन्य मामलों पर विचार करें और मैं उन्हें कैसे संभाल सकता हूं। लेकिन पहले, मान लें कि हम parse_abe_statusसंभव तरीके से मूर्खतापूर्ण तरीके से लागू करते हैं:

def parse_abe_status(s):
    return s[13:]

अर्थात्, यह किसी भी त्रुटि की जाँच नहीं करता है। अब, विकल्पों पर:

विकल्प 1: वापसी None

मैं फोन करने वाले को बता सकता हूं कि कुछ गलत हो गया था None:

def get_abe_status(url):
    # download the page
    page = download_page(url)

    # get all mentions of Abe Vigoda
    hits = page.find_all_mentions("Abe Vigoda")

    if not hits:
        return None

    # parse the first hit for his status
    status = parse_abe_status(hits[0])

    # he's either alive or dead
    return status == "alive"

यदि कॉल करने वाले को Noneमेरे कार्य से प्राप्त होता है, तो उसे यह मान लेना चाहिए कि अबे विगोडा का कोई उल्लेख नहीं था, और इसलिए कुछ गलत हुआ। लेकिन यह बहुत अस्पष्ट है, है ना? और यह उस मामले में मदद नहीं करता है जहां hits[0]हमने सोचा नहीं था कि यह क्या था।

दूसरी ओर, हम कुछ अपवादों में रख सकते हैं:

विकल्प 2: अपवादों का उपयोग करना

यदि hitsहम खाली हैं, IndexErrorतो जब हम प्रयास करेंगे तो फेंक दिया जाएगा hits[0]। लेकिन फोन करने वाले से यह अपेक्षा नहीं की जानी चाहिए कि IndexErrorवह मेरे फंक्शन द्वारा फेंके गए हैंडल को संभाल सके , क्योंकि उसे पता नहीं है कि वह कहाँ IndexErrorसे आया है; यह find_all_mentionsसभी के लिए, वह जानता है , द्वारा फेंका जा सकता था । तो हम इसे संभालने के लिए एक कस्टम अपवाद क्लास बनाएंगे:

class NotFoundError(Exception):
    """Throw this when something can't be found on a page."""

def get_abe_status(url):
    # download the page
    page = download_page(url)

    # get all mentions of Abe Vigoda
    hits = page.find_all_mentions("Abe Vigoda")

    try:
        hits[0]
    except IndexError:
        raise NotFoundError("No mentions found.")

    # parse the first hit for his status
    status = parse_abe_status(hits[0])

    # he's either alive or dead
    return status == "alive"

अब क्या होगा अगर पेज बदल गया है और अप्रत्याशित संख्या में हिट हैं? यह भयावह नहीं है, क्योंकि कोड अभी भी काम कर सकता है, लेकिन एक कॉलर अतिरिक्त सावधानी बरतना चाहता है , या वह चेतावनी देना चाहता हो सकता है। तो मैं एक चेतावनी फेंक दूंगा:

class NotFoundError(Exception):
    """Throw this when something can't be found on a page."""

def get_abe_status(url):
    # download the page
    page = download_page(url)

    # get all mentions of Abe Vigoda
    hits = page.find_all_mentions("Abe Vigoda")

    try:
        hits[0]
    except IndexError:
        raise NotFoundError("No mentions found.")

    # say we expect four hits...
    if len(hits) != 4:
        raise Warning("An unexpected number of hits.")
        logger.warning("An unexpected number of hits.")

    # parse the first hit for his status
    status = parse_abe_status(hits[0])

    # he's either alive or dead
    return status == "alive"

अंत में, हम पा सकते हैं कि statusया तो जीवित या मृत नहीं है। हो सकता है, किसी अजीब कारण से, आज यह हो गया comatose। तब मैं वापस नहीं लौटना चाहता False, क्योंकि इसका मतलब है कि अबे मर चुका है। मुझे यहां क्या करना चाहिए? एक अपवाद फेंको, शायद। लेकिन किस तरह का? क्या मुझे एक कस्टम अपवाद वर्ग बनाना चाहिए?

class NotFoundError(Exception):
    """Throw this when something can't be found on a page."""

def get_abe_status(url):
    # download the page
    page = download_page(url)

    # get all mentions of Abe Vigoda
    hits = page.find_all_mentions("Abe Vigoda")

    try:
        hits[0]
    except IndexError:
        raise NotFoundError("No mentions found.")

    # say we expect four hits...
    if len(hits) != 4:
        raise Warning("An unexpected number of hits.")
        logger.warning("An unexpected number of hits.")

    # parse the first hit for his status
    status = parse_abe_status(hits[0])

    if status not in ['alive', 'dead']:
        raise SomeTypeOfError("Status is an unexpected value.")

    # he's either alive or dead
    return status == "alive"

विकल्प 3: कहीं बीच में

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

जवाबों:


17

पायथन में सिफारिश विफलता का संकेत करने के लिए अपवादों का उपयोग करने के लिए है। यदि आप नियमित आधार पर असफलता की उम्मीद करते हैं तो भी यह सच है।

इसे अपने कोड के कॉलर के नजरिए से देखें:

my_status = get_abe_status(my_url)

अगर हम कोई नहीं लौटाते तो क्या होगा? यदि कॉलर विशेष रूप से उस मामले को नहीं संभालता है जो get_abe_status विफल हो गया है, तो यह my_stats के साथ जारी रखने की कोशिश करेगा। यह बाद में बग का निदान करने के लिए एक मुश्किल पैदा कर सकता है। यहां तक ​​कि अगर आप किसी के लिए भी जांच करते हैं, तो इस कोड का कोई सुराग नहीं है कि get_abe_status () विफल क्यों है।

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

कॉलर के दृष्टिकोण से, रिटर्न मान की तुलना में अपवाद प्राप्त करने के लिए इसका बस अधिक सुविधाजनक है। और वह अजगर शैली है, जो असफलताओं की स्थिति का संकेत देने के लिए अपवादों का उपयोग करती है और मूल्यों को वापस नहीं करती है।

कुछ एक अलग दृष्टिकोण लेंगे और तर्क देंगे कि आपको केवल उन मामलों के लिए अपवादों का उपयोग करना चाहिए जिन्हें आप वास्तव में होने की उम्मीद नहीं करते हैं। उनका तर्क है कि सामान्य रूप से चलने वाले रन किसी भी अपवाद को नहीं बढ़ा सकते हैं। इसका एक कारण यह बताया गया है कि अपवाद स्थाई रूप से अक्षम हैं, लेकिन यह वास्तव में अजगर के लिए सही नहीं है।

आपके कोड पर कुछ बिंदु:

try:
    hits[0]
except IndexError:
    raise NotFoundError("No mentions found.")

खाली सूची की जांच करने का यह वास्तव में भ्रामक तरीका है। केवल कुछ की जाँच करने के लिए एक अपवाद उत्पन्न न करें। अगर का उपयोग करें।

# say we expect four hits...
if len(hits) != 4:
    raise Warning("An unexpected number of hits.")
    logger.warning("An unexpected number of hits.")

आपको लगता है कि logger.warning लाइन कभी सही नहीं चलेगी?


1
आपकी प्रतिक्रिया के लिए धन्यवाद (विश्वासपूर्वक)। यह प्रकाशित कोड को देखने के साथ-साथ, अपवाद कब और कैसे फेंकना है, इसके लिए मेरी भावना में सुधार हुआ है।
जेम १

4

स्वीकृत उत्तर स्वीकार किए जाने योग्य है और प्रश्न का उत्तर देता है, मैं इसे केवल अतिरिक्त पृष्ठभूमि प्रदान करने के लिए लिखता हूं।

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

मैं आपको यह दिखाने के लिए एक उदाहरण प्रदान करना चाहता हूं कि C ++ / Java से मानसिकता में कितना नाटकीय अंतर है। C ++ में लूप के लिए आमतौर पर कुछ ऐसा दिखता है:

for(int i = 0; i != myvector.size(); ++i) ...

इस बारे में सोचने का एक तरीका: myvector[k]जहां k> = myvector.size () तक पहुंचना अपवाद का कारण होगा। तो आप सिद्धांत रूप में इसे (बहुत अजीब तरह से) एक कोशिश-कैच के रूप में लिख सकते हैं।

    for(int i = 0; ; ++i)  {
        try {
           ...
        } catch (& std::out_of_range)
             break

या ऐसा ही कुछ। अब, विचार करें कि लूप के लिए अजगर में क्या हो रहा है:

for i in range(1):
    ...

यह कैसे काम कर रहा है? लूप के लिए रेंज (1) का परिणाम होता है और उस पर पुनरावृत्त () कॉल करता है, जो इसे पुनरावृत्त करता है।

b = range(1).__iter__()

तब तक यह प्रत्येक लूप पुनरावृत्ति पर इसके आगे कहता है, जब तक ...:

>>> next(b)
0
>>> next(b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

दूसरे शब्दों में, अजगर के लिए एक लूप वास्तव में एक कोशिश है-भेस को छोड़कर।

जहां तक ​​ठोस सवाल है, याद रखें कि अपवाद सामान्य कार्य निष्पादन को रोकते हैं और अलग से निपटा जाना चाहिए। पायथन में, आपको स्वतंत्र रूप से उन्हें फेंक देना चाहिए जब भी आपके फ़ंक्शन में बाकी कोड को निष्पादित करने का कोई बिंदु नहीं होता है, और / या रिटर्न में से कोई भी सही ढंग से कार्य में दिखाई नहीं देता है। ध्यान दें कि किसी फ़ंक्शन से जल्दी लौटना अलग है: जल्दी लौटने का मतलब है कि आपने पहले ही उत्तर का पता लगा लिया है और उत्तर जानने के लिए बाकी कोड की आवश्यकता नहीं है। मैं कह रहा हूं कि जब उत्तर ज्ञात नहीं है तो अपवादों को फेंक दिया जाना चाहिए, और उत्तर को निर्धारित करने के लिए कोड के बाकी हिस्सों को यथोचित रूप से नहीं चलाया जा सकता है। अब, खुद को "सही ढंग से प्रतिबिंबित करें", जैसे कि आप किन अपवादों को फेंकना चुनते हैं, यह सब प्रलेखन का मामला है।

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

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


2

असाधारण कुछ होने पर आपको अपवादों का उपयोग करना चाहिए । यही है, कुछ ऐसा होना चाहिए जो एप्लिकेशन का उचित उपयोग न करे। यदि आपके विधि के उपभोक्ता के लिए यह स्वीकार्य और अपेक्षित है कि कुछ ऐसा खोजा जाए जो नहीं मिलेगा, तो "नहीं मिला" एक असाधारण मामला नहीं है। इस स्थिति में, आपको रिक्त या "कोई नहीं" या {}, या कुछ खाली रिटर्न सेट का संकेत देना चाहिए।

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

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


1
यदि आप तय करते हैं कि मूल्य नहीं मिल रहा है, तो इस बात से सावधान रहें कि आप यह बताने के लिए क्या उपयोग करते हैं। यदि आपकी विधि को वापस करना है Stringऔर आप अपने संकेतक के रूप में "कोई नहीं" चुनते हैं, तो इसका मतलब है कि आपको सावधान रहना होगा कि "कोई भी" एक वैध मूल्य नहीं होगा। यह भी ध्यान दें कि डेटा को देखने और मूल्य नहीं ढूंढने और डेटा को पुनः प्राप्त करने में सक्षम नहीं होने के बीच अंतर है, इस प्रकार हम डेटा को नहीं ढूंढ सकते हैं। इन दो मामलों के लिए एक ही परिणाम होने का अर्थ है कि आपके पास कोई दृश्यता नहीं है जब आप एक होने की उम्मीद करते हैं तो आपको कोई मूल्य नहीं मिलता है।
अनहेल्स्लेमर

इनलाइन कोड ब्लॉक को बैकटिक्स (`) के साथ चिह्नित किया जाता है, शायद यही है कि आपको" कोई नहीं "के साथ क्या करना है?
इज़्काता

3
मुझे डर है कि यह पायथन में बिल्कुल गलत है। आप C ++ / Java शैली तर्क को किसी अन्य भाषा में लागू कर रहे हैं। लूप के अंत को इंगित करने के लिए पायथन अपवादों का उपयोग करता है; यह बहुत अस्पष्ट है।
नीर फ्रीडमैन

2

अगर मैं एक फ़ंक्शन लिख रहा था

 def abe_is_alive():

मैं यह करने के लिए लिखते थे return Trueया Falseऐसे मामलों में जहां मैं एक या दूसरे की बात को पूरी तरह कर रहा हूँ, और raiseकिसी भी अन्य मामले (जैसे में किसी त्रुटि raise ValueError("Status neither 'dead' nor 'alive'"))। ऐसा इसलिए है क्योंकि फ़ंक्शन कॉलिंग माइन एक बूलियन की उम्मीद कर रहा है, और अगर मैं यह सुनिश्चित नहीं कर सकता कि नियमित कार्यक्रम प्रवाह जारी नहीं होना चाहिए।

उम्मीद की तुलना में "हिट" की एक अलग संख्या प्राप्त करने के आपके उदाहरण की तरह, मैं शायद अनदेखा करूंगा; जब तक हिट में से एक अभी भी मेरे पैटर्न से मेल खाता है "अबे विगोडा {मृत | जीवित}" है, यह ठीक है। यह पृष्ठ को फिर से व्यवस्थित करने की अनुमति देता है लेकिन फिर भी उपयुक्त जानकारी मिलती है।

बजाय

try:
    hits[0] 
except IndexError:
    raise NotFoundError

मैं स्पष्ट रूप से जांच करूंगा:

if not hits:
    raise NotFoundError

जैसा कि यह "सस्ता" हो जाता है तब स्थापित होता है try

मैं आपसे सहमत हूँ IOError; मैं वेबसाइट से कनेक्ट करने में त्रुटि को संभालने की कोशिश नहीं करूंगा - यदि हम किसी कारण से नहीं कर सकते हैं, तो इसे संभालने के लिए उपयुक्त स्थान नहीं है (क्योंकि यह हमारे प्रश्न का उत्तर देने में मदद नहीं करता है) और इसे पास होना चाहिए कॉलिंग फ़ंक्शन के लिए।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.