2009-06-12 19 views
26

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

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

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

आपके विचार के लिए, एक उदाहरण:

try: 
    server = smtplib.SMTP(host) #can throw an exception 
except smtplib.socket.gaierror: 
    #actually it can throw a lot more, this is just an example 
    pass 
else: #only if no exception was thrown we may continue 
    try: 
     server.login(username, password) 
    except SMTPAuthenticationError: 
     pass # do some stuff here 
    finally: 
     #we can only run this when the first try...except was successful 
     #else this throws an exception itself! 
     server.quit() 
    else: 
     try: 
      # this is already the 3rd nested try...except 
      # for such a simple procedure! horrible 
      server.sendmail(addr, [to], msg.as_string()) 
      return True 
     except Exception: 
      return False 
     finally: 
      server.quit() 

return False 

यह मेरे लिए अत्यंत unpythonic लग रहा है, और कोड से निपटने त्रुटि ट्रिपल वास्तविक व्यापार कोड है, लेकिन दूसरी ओर मैं कई बयान हैं कि कैसे संभाल कर सकते हैं एक दूसरे पर निर्भर, अर्थात् कथन 1 कथन 2 के लिए पूर्व शर्त है और इसी तरह?

मुझे उचित संसाधन सफाई में भी रूचि है, यहां तक ​​कि पायथन भी इसे अपने लिए प्रबंधित कर सकता है।

धन्यवाद, टॉम

+1

डीबीआर संपादित करने के लिए धन्यवाद, लेकिन कृपया उन चीज़ों को संपादित न करें जिन्हें आप अपने बारे में निश्चित नहीं हैं। मैंने मानदंड के मानदंडों को संपादित किया है, जो वास्तव में एकवचन है, क्योंकि बहुवचन को यह समझ में नहीं आता है कि आपने इसे संपादित किया है। – Tom

+0

ओप, इसके बारे में खेद है (एचएम, मुझे नहीं लगता कि मैंने कभी पहले मानदंडों का एकवचन सुना है ..) – dbr

उत्तर

24

इसके बजाय/छोड़कर बाकी ब्लॉक देता है तो आप वापस आ सकता है जब यह त्रुटियों:

def send_message(addr, to, msg): 
    ## Connect to host 
    try: 
     server = smtplib.SMTP(host) #can throw an exception 
    except smtplib.socket.gaierror: 
     return False 

    ## Login 
    try: 
     server.login(username, password) 
    except SMTPAuthenticationError: 
     server.quit() 
     return False 

    ## Send message 
    try: 
     server.sendmail(addr, [to], msg.as_string()) 
     return True 
    except Exception: # try to avoid catching Exception unless you have too 
     return False 
    finally: 
     server.quit() 

कि पूरी तरह से पठनीय और pythonic है ..

ऐसा करने का एक और तरीका है, बल्कि चिंता से विशिष्ट कार्यान्वयन के बारे में यह तय कर लें तो आप अपने कोड उदाहरण के लिए, देखने के लिए चाहते हैं ..

sender = MyMailer("username", "password") # the except SocketError/AuthError could go here 
try: 
    sender.message("addr..", ["to.."], "message...") 
except SocketError: 
    print "Couldn't connect to server" 
except AuthError: 
    print "Invalid username and/or password!" 
else: 
    print "Message sent!" 

फिर message() विधि, सीए के लिए कोड लिखने आप जिस भी त्रुटि की अपेक्षा करते हैं उसे खींचते हैं, और अपना खुद का कस्टम उठाते हैं, और इसे संभालते हैं जहां यह प्रासंगिक है। आपका वर्ग कुछ लग सकता है की तरह ..

class ConnectionError(Exception): pass 
class AuthError(Exception): pass 
class SendError(Exception): pass 

class MyMailer: 
    def __init__(self, host, username, password): 
     self.host = host 
     self.username = username 
     self.password = password 

    def connect(self): 
     try: 
      self.server = smtp.SMTP(self.host) 
     except smtplib.socket.gaierror: 
      raise ConnectionError("Error connecting to %s" % (self.host)) 

    def auth(self): 
     try: 
      self.server.login(self.username, self.password) 
     except SMTPAuthenticationError: 
      raise AuthError("Invalid username (%s) and/or password" % (self.username)) 

    def message(self, addr, to, msg): 
     try: 
      server.sendmail(addr, [to], msg.as_string()) 
     except smtplib.something.senderror, errormsg: 
      raise SendError("Couldn't send message: %s" % (errormsg)) 
     except smtp.socket.timeout: 
      raise ConnectionError("Socket error while sending message") 
+4

+1 मुझे वास्तव में यह पसंद है कि आपने "लाइब्रेरी केवल सबकुछ के लिए एक अपवाद का उपयोग किया है" समस्या –

+1

आपके पहले उदाहरण में, send_message() हमेशा server.login() के बाद वापस आ जाएगा और संदेश कभी नहीं भेजेगा। मुझे नहीं लगता कि इस कथन के लिए अंत में होना चाहिए। – mhawke

+1

तो अब यह सिद्धांत के एक सवाल के लिए उबाल जाता है।आपका पहला कोड मूल रूप से मेरा जैसा ही है, सिवाय इसके कि आप बस अपवादों को घोंसला नहीं करते हैं, जैसा कि मैंने "अन्य" -ट्री में किया था, जिसे पायथन दस्तावेज़ों में सुझाया गया था। कौन सा बेहतर अभ्यास है? दस्तावेज़ों का कहना है कि कोशिश ब्लॉक में अतिरिक्त बयानों के बजाय हमेशा पसंदीदा होना चाहिए। इसका मूल रूप से वही प्रश्न है यदि: क्या यह किसी और से पहले वापस लौटना बेहतर है, या यह सशर्त रूप से ifs को घोंसला करना बेहतर है। – Tom

0

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

+0

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

+0

ब्लॉक के साथ Nested, तो? http://docs.python.org/whatsnew/2.5.html#pe पी -343 –

+0

दुख की बात है कि मैं उन सभी परिचालनों के लिए परिभाषित करने के लिए __enter__ और __exit__ विधियों पर भरोसा नहीं कर सकता, इसलिए मैं हमेशा – Tom

12

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

try: 
    server = smtplib.SMTP(host) 
    server.login(username, password) # Only runs if the previous line didn't throw 
    server.sendmail(addr, [to], msg.as_string()) 
    return True 
except smtplib.socket.gaierror: 
    pass # Couldn't contact the host 
except SMTPAuthenticationError: 
    pass # Login failed 
except SomeSendMailError: 
    pass # Couldn't send mail 
finally: 
    if server: 
     server.quit() 
return False 

यहाँ, हम इस तथ्य का उपयोग करें कि smtplib.SMTP(), server.login(), और server.sendmail() सभी समतल करने के लिए विभिन्न अपवाद फेंक कोशिश-पकड़ ब्लॉक का पेड़। अंत में ब्लॉक हम शून्य ऑब्जेक्ट पर छोड़ने() को आमंत्रित करने से बचने के लिए स्पष्ट रूप से सर्वर का परीक्षण करते हैं।

हम भी तीन अनुक्रमिक कोशिश पकड़ ब्लॉक इस्तेमाल कर सकते हैं, अपवाद की स्थिति में झूठी लौटने, अगर वहाँ ओवरलैपिंग अपवाद मामलों को अलग से नियंत्रित किया जा करने की आवश्यकता है:

try: 
    server = smtplib.SMTP(host) 
except smtplib.socket.gaierror: 
    return False # Couldn't contact the host 

try: 
    server.login(username, password) 
except SMTPAuthenticationError: 
    server.quit() 
    return False # Login failed 

try: 
    server.sendmail(addr, [to], msg.as_string()) 
except SomeSendMailError: 
    server.quit() 
    return False # Couldn't send mail 

return True 

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

+5

बिंदु जैसा ऊपर बताया गया है, कि वे अलग-अलग अपवाद नहीं फेंकते हैं, इसलिए इसे छोटा रूप से फ़्लैट नहीं किया जा सकता है। अगर आपके कनेक्शन को ऑथ करने से पहले बाधित हो जाता है, server.login और server.sendMail एक ही अपवाद फेंक सकता है ("पहले सर्वर से कनेक्ट करें") लेकिन जैसा कि मैंने उपरोक्त भी कहा है, मैं इस विशिष्ट के समाधान की तलाश नहीं कर रहा हूं मुसीबत। मैं इसे हल करने के लिए एक सामान्य दृष्टिकोण में अधिक रुचि रखता हूं। आपका दूसरा दृष्टिकोण मूल रूप से "अन्य" के बिना मेरा कोड है। यह और अधिक सुंदर है हालांकि मुझे स्वीकार करना है;) – Tom

+1

आखिरकार ब्लॉक पर देखें - आप किसी मौजूदा अस्तित्व को गलती से संदर्भित करने से बचने के लिए ब्लॉक से पहले सर्वर को सेट नहीं करना चाहेंगे। –

+0

@ टॉम +1, यही कारण है कि मैंने इस समाधान का सुझाव नहीं दिया। – Unknown

0

मुझे डेविड का जवाब पसंद है, लेकिन यदि आप सर्वर अपवादों पर फंस गए हैं तो आप सर्वर या किसी भी राज्य के लिए भी जांच सकते हैं। मैंने थोड़ी सी विधि को फटकार दिया, यह अभी भी एक लेकिन अवांछित दिख रहा है लेकिन नीचे तर्क में अधिक पठनीय है।

server = None 

def server_obtained(host): 
    try: 
     server = smtplib.SMTP(host) #can throw an exception 
     return True 
    except smtplib.socket.gaierror: 
     #actually it can throw a lot more, this is just an example 
     return False 

def server_login(username, password): 
    loggedin = False 
    try: 
     server.login(username, password) 
     loggedin = True 
    except SMTPAuthenticationError: 
     pass # do some stuff here 
    finally: 
     #we can only run this when the first try...except was successful 
     #else this throws an exception itself! 
     if(server is not None): 
      server.quit() 
    return loggedin 

def send_mail(addr, to, msg): 
    sent = False 
    try: 
     server.sendmail(addr, to, msg) 
     sent = True 
    except Exception: 
     return False 
    finally: 
     server.quit() 
    return sent 

def do_msg_send(): 
    if(server_obtained(host)): 
     if(server_login(username, password)): 
      if(send_mail(addr, [to], msg.as_string())): 
       return True 
    return False 
+0

दोनों सर्वर_लॉगिन और send_mail में आप स्थानीय चर से बच सकते हैं, क्योंकि आखिर में हमेशा निष्पादित हो जाएगा, भले ही आप कोशिश में या "ब्लॉक" को छोड़कर "वापसी" का उपयोग करें :) आप बस कोशिश ब्लॉक में ट्रू को वापस कर सकते हैं, और गलत में छोड़कर गलत एक स्थानीय var में राज्य को बचाने के बजाय ब्लॉक। – Tom

1

बस एक कोशिश-ब्लॉक का उपयोग करने का तरीका है। यह वही है जो वे के लिए डिज़ाइन किए गए हैं: पिछले कथन ने अपवाद फेंकने पर केवल अगले कथन को निष्पादित किया है। संसाधन क्लीन-अप के लिए, शायद यदि आप (जैसे Myfile को साफ़ करने की आवश्यकता है तो आप संसाधन की जांच कर सकते हैं।is_open(), ...) यह कुछ अतिरिक्त शर्तों को जोड़ता है, लेकिन वे केवल असाधारण मामले में निष्पादित किए जाएंगे। केस को संभालने के लिए कि अलग-अलग कारणों से एक ही अपवाद उठाया जा सकता है, आप अपवाद से कारण पुनर्प्राप्त करने में सक्षम होना चाहिए।

मैं इस तरह कोड का सुझाव:

server = None 
try: 
    server = smtplib.SMTP(host) #can throw an exception 
    server.login(username, password) 
    server.sendmail(addr, [to], msg.as_string()) 
    server.quit() 
    return True 
except smtplib.socket.gaierror: 
    pass # do some stuff here 
except SMTPAuthenticationError: 
    pass # do some stuff here 
except Exception, msg: 
    # Exception can have several reasons 
    if msg=='xxx': 
     pass # do some stuff here 
    elif: 
     pass # do some other stuff here 

if server: 
    server.quit() 

return False 

यह है कोई असामान्य, कोड से निपटने कि त्रुटि व्यवसाय कोड से अधिक है। सही त्रुटि हैंडलिंग जटिल हो सकती है। लेकिन रखरखाव बढ़ाने के लिए यह त्रुटि कोडिंग कोड से व्यवसाय कोड को अलग करने में मदद करता है। कोशिश का उपयोग करने का

+0

मैं असहमत हूं, क्योंकि जैसा कि मैंने उपरोक्त कुछ बार कहा है, त्रुटि संदेश संदिग्ध होंगे, 2 अलग-अलग फ़ंक्शन कॉल के रूप में, उदाहरण के लिए लॉगिन और sendmail एक ही अपवाद फेंक सकता है। अगर आप उपयोगकर्ता को प्रिंट करना चाहते हैं, या अपने लॉग में: "xyz" या "sendmail() xyz की वजह से विफल हुआ" यह विफल नहीं है ", यह संभव नहीं है, क्योंकि दोनों कॉल के परिणामस्वरूप एक ही अपवाद हो सकता है। मैं लॉगिंग उद्देश्यों के लिए क्या गलत हो गया विस्तृत प्रबंधन करना चाहता हूं। – Tom

+0

अपवाद इसके विवरण प्रदान करने में सक्षम होना चाहिए। जैसे आप "गियरर को छोड़कर, (कोड, संदेश) को छोड़कर" उपयोग कर सकते हैं: ", सरल के बजाय" गियरर को छोड़कर: "। फिर आपके पास त्रुटि कोड और त्रुटि संदेश है और विस्तृत त्रुटि प्रबंधन के लिए उनका उपयोग कर सकते हैं, उदा। अगर कोड == 11001: प्रिंट करें "अज्ञात होस्ट नाम:", संदेश – Ralph

+0

मुझे लगता है कि आप जो कुछ कहने की कोशिश कर रहे थे उसे पूरी तरह से प्राप्त नहीं किया था। निम्न का प्रयास करें: स्वयं को एक एसएमटीपी ऑब्जेक्ट बनाएं, और फिर कनेक्ट किए बिना smtp.login() को कोशिश करें, और उसके बाद smtp.sendmail() कनेक्ट किए बिना, आप देखेंगे कि वे 100% समान अपवाद फेंकते हैं, जिन्हें आप अलग नहीं कर सकते, न ही संदेश द्वारा errno – Tom

3

अगर यह मैं था मैं शायद निम्नलिखित की तरह कुछ करना होगा: सफलता पर

try: 
    server = smtplib.SMTP(host) 
    try: 
     server.login(username, password) 
     server.sendmail(addr, [to], str(msg)) 
    finally: 
     server.quit() 
except: 
    debug("sendmail", traceback.format_exc().splitlines()[-1]) 
    return True 

सभी त्रुटियों को पकड़ लिया और डिबग, वापसी मान रहे == सच , और प्रारंभिक कनेक्शन किए जाने पर सर्वर कनेक्शन ठीक से साफ़ किया जाता है। अगर वे सफल हो गए वे सच लौटने के लिए, और जब तक वे वास्तव में संभाल एक अपवाद:

class Mailer(): 

    def send_message(self): 
     exception = None 
     for method in [self.connect, 
         self.authenticate, 
         self.send, 
         self.quit]: 
      try: 
       if not method(): break 
      except Exception, ex: 
       exception = ex 
       break 

     if method == quit and exception == None: 
      return True 

     if exception: 
      self.handle_exception(method, exception) 
     else: 
      self.handle_failure(method) 

    def connect(self): 
     return True 

    def authenticate(self): 
     return True 

    def send(self): 
     return True 

    def quit(self): 
     return True 

    def handle_exception(self, method, exception): 
     print "{name} ({msg}) in {method}.".format(
      name=exception.__class__.__name__, 
      msg=exception, 
      method=method.__name__) 

    def handle_failure(self, method): 
     print "Failure in {0}.".format(method.__name__) 

सभी पद्धतियों का (send_message सहित, वास्तव में) का पालन ही प्रोटोकॉल:

+0

यह मेरे लिए सहज दिखता है, और शायद यह जावा में जैसा दिखता है, क्योंकि आपके पास "अन्य" कथन की लक्जरी नहीं है। आपके पास python2.5 iirc से पहले यह नहीं था। इसे बड़े प्रयास ब्लॉक से बचने के लिए दस्तावेज़ों में पेश किया गया था लेकिन फिर भी समूह को अच्छा और स्पष्ट रखना है, इसलिए सभी कोड जो एक साथ हैं, वे अभी भी एक ही प्रयास में होंगे ... सिवाय इसके अलावा ... अन्यथा ब्लॉक को बाहर निकालने के बिना proprtions। चूंकि पाइथोनियन अपनी स्टाइल गाइड और पेप्स से इतने जुड़े हुए हैं, मुझे लगा कि यह जाने का सही तरीका होगा। – Tom

1

मैं कुछ इस तरह की कोशिश करेगा वे इसे फंस नहीं करते हैं। यह प्रोटोकॉल उस मामले को संभालना भी संभव बनाता है जहां एक विधि को इंगित करने की आवश्यकता होती है कि यह अपवाद उठाए बिना विफल रहा। (यदि आपके तरीकों में असफल होने का एकमात्र तरीका अपवाद उठाकर है, तो प्रोटोकॉल को सरल बना दिया जाता है। यदि आपको विफल होने वाली विधि के बाहर कई गैर-अपवाद विफलता राज्यों से निपटना पड़ता है, तो संभवतः आपके पास एक डिज़ाइन समस्या है अभी तक काम नहीं किया है।)

इस दृष्टिकोण का नकारात्मक पक्ष यह है कि सभी विधियों को एक ही तर्क का उपयोग करना होगा। मैंने उम्मीद की है कि मैंने जिस तरीके से स्टब किया है, वह वर्ग के सदस्यों को जोड़ना समाप्त कर देगा।

हालांकि, इस दृष्टिकोण का उछाल काफी महत्वपूर्ण है। सबसे पहले, आप बिना किसी जटिल होने के send_message के बिना प्रक्रिया में दर्जनों तरीकों को जोड़ सकते हैं।

तुम भी पागल जाने के लिए और कुछ इस तरह कर सकते हैं:

def handle_exception(self, method, exception): 
    custom_handler_name = "handle_{0}_in_{1}".format(\ 
              exception.__class__.__name__, 
              method.__name__) 
    try: 
     custom_handler = self.__dict__[custom_handler_name] 
    except KeyError: 
     print "{name} ({msg}) in {method}.".format(
      name=exception.__class__.__name__, 
      msg=exception, 
      method=method.__name__) 
     return 
    custom_handler() 

def handle_AuthenticationError_in_authenticate(self): 
    print "Your login credentials are questionable." 

... हालांकि उस बिंदु पर, मैं अपने आप से कह सकते हैं, "आत्म, आप कमान पैटर्न काम कर रहे हैं बहुत मुश्किल बनाने के बिना एक कमांड क्लास। शायद अब समय है।"

संबंधित मुद्दे