2009-06-12 4 views
26

다른 게시물을 검색했습니다.이 질문은 다소 일반적인 문제이지만 다른 모든 Python 예외 질문은 내 문제를 반영하지 않았습니다.Python에서 예외를 처리하는 올바른 방법은 무엇입니까?

내가 할 수있는 한 구체적으로하려고 노력할 것이므로 직접적인 예를 들어 보겠습니다. 그리고 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보고, 오류 처리 코드가 트리플 실제 비즈니스 코드이지만, 다른 한편으로는 어떻게 몇 가지 문을 처리 할 수 ​​있습니다 서로 의존적으로, statement1을 의미하는 것은 statement2에 대한 전제 조건이다.

나는 또한 적절한 리소스 정리에 관심이 있습니다. 심지어 파이썬이 스스로 관리 할 수 ​​있습니다.

감사합니다, Tom

+1

dbr을 편집 해 주셔서 감사합니다.하지만 자신에 대해 잘 모르는 것을 편집하지 마십시오. 기준에 대한 기준을 다시 편집했습니다. 단, 복수형은 편집 할 때 의미가 없습니다. – Tom

+0

Opps, 죄송합니다. (hm, 전에는 단 하나의 기준을 들어 본 적이 없다고 생각하지 마십시오.) – dbr

답변

24

대신 시도를 사용하는 다른 블록라는 점만 제외하면이 오류가 때/당신은 단순히 반환 할 수 특정 구현에 대해, 당신은 당신의 코드는, 예를 들어,보고 방법을 결정 ..

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() 방법, CA의 코드를 작성 기대하는 오류를 찾아 내고 자신 만의 오류를 제기하고 관련성이있는 곳에서 처리하십시오. 성공

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 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

그러면 이제는 원칙의 문제로 귀결됩니다.첫 번째 코드는 기본적으로 내 코드와 동일합니다. 파이썬 문서에 제안 된 "else"트리에서했던 것처럼 예외를 중첩하지 않습니다. 어느 것이 더 나은 관행인가? 워드 프로세서는 try 블록의 추가 명령문 대신 항상 else를 선호해야한다고 명시합니다. 그 기본적으로 같은 질문 : 만약 다른 경우 전에 반환하는 것이 좋습니다 또는 ifs 조건부로 중첩하는 것이 좋습니다. – Tom

0

왜 큰 시도 : 블록? 이렇게하면 예외가 잡히면 예외로 끝까지갑니다. 그리고 다른 단계에 대한 모든 예외가 다르면 예외를 해고 한 부분을 항상 알 수 있습니다.

+0

하나의 큰 try 블록이 당신에게 ressources를 돌볼 기회를주지 않을 것이기 때문에. 예 : statement1이 열리고 a 파일하지만 문 2 예외를 throw합니다. 리소스를 실제로 할당 된 여부를 알 수 없기 때문에 하나의 단일 finally 블록에서 처리 할 수 ​​없습니다. 또한 일부 단계는 동일한 예외를 발생시킬 수 있으므로 잘못 된 무엇이 결정할 수 없습니다. 나중에 어떤 명령문이 실제로 실패했는지 모르기 때문에 인쇄 오류가 발생하지 않습니다. – Tom

+0

블록으로 중첩 된 경우, http://docs.python.org/whatsnew/2.5.html#pe p-343 –

+0

슬프게도 나는 __enter__ 및 __exit__ 메서드를 사용하여 모든 작업에 대해 정의 할 수 있으므로 항상 작동하지 않을 수 있습니다. – Tom

12

일반적으로 발생할 수있는 예외 종류로 오류 조건을 구분하여 가능한 한 적은 try 블록을 사용하려고합니다. 예를 들어, 여기 당신이 게시 코드의 내 리팩토링이다 : 여기

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-catch 블록의 트리. finally 블록에서 서버를 명시 적으로 테스트하여 nil 객체에서 quit()을 호출하지 않도록합니다.

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 

이 확실히되지 않습니다 : 별도로 처리해야 중복 예외의 경우가있을 경우 우리는 또한, 예외 조건에서 false를 반환, 세 연속 시도-catch 블록을 사용할 수

좋은 점은 하나 이상의 장소에서 서버를 종료해야하기 때문에, 이제 여분의 상태를 유지하지 않고도 다른 장소에서 특정 예외 유형을 다른 방식으로 처리 할 수 ​​있습니다.

+5

위와 같이 요점은 개별 예외를 throw하지 않으므로 쉽게 평탄화 될 수 없습니다. 서버를 인증하기 전에 연결이 중단 된 경우 server.login과 server.sendMail이 동일한 예외 ("서버에 먼저 연결")를 throw 할 수 있습니다. 하지만 위에서 설명한 것처럼이 특정 솔루션을 찾는 것이 아닙니다. 문제. 나는 이것을 해결하는 일반적인 방법에 더 관심이있다. 두 번째 접근 방식은 기본적으로 "else"가없는 코드입니다. 그 더 예쁜 나는 인정해야한다;) – Tom

+1

마침내 블록에 조심해라. - 존재하지 않는 변수를 우연히 참조하는 것을 피하기 위해 서버 앞에 None을 설정하기를 원할 것이다. –

+0

@Tom +1, 그래서이 솔루션을 제안하지 않았습니다. – Unknown

0

나는 David의 답변을 좋아하지만 서버 예외에 붙어있는 경우 없음 또는 상태 인 경우 서버를 확인할 수도 있습니다. 나는 약간의 방법을 평평하게했다. 그러나 그것은 여전히 ​​보잘 것 없다. 그러나 바닥에있는 논리에서 더 읽기 쉽다.

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

모두 server_login과 send_mail에서 로컬 변수를 피할 수 있습니다. try 또는 except 블록에서 "return"을 사용하더라도 finally가 항상 실행되기 때문에 try 블록에서 True를 반환하고 except 로컬 변수에 상태를 저장하는 대신 차단하십시오. – Tom

1

단지 하나의 try 블록을 사용하면됩니다. 정확히 은 다음과 같은 목적으로 설계되었습니다. 이전 문이 예외를 throw하지 않은 경우에만 다음 명령문을 실행하십시오. 리소스 정리의 경우 리소스를 정리해야 할 수도 있습니다. (예 : 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 

그것은 코드를 처리하는 오류가 비즈니스 코드를 더 드문 초과하지 않습니다. 올바른 오류 처리는 복잡 할 수 있습니다. 그러나 유지 관리 가능성을 높이려면 비즈니스 코드를 오류 처리 코드와 분리하는 것이 좋습니다. 완벽하게 읽을 수와 파이썬의

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() 

..

이 일을하는 또 다른 방법입니다 오히려 걱정보다, :

+0

나는 2 ~ 3 가지 다른 함수 호출, 예를 들어 login과 sendmail이 동일한 예외를 던질 수 있기 때문에 위의 몇 번 언급 한 바와 같이 오류 메시지가 모호 할 수 있기 때문에 동의하지 않습니다. 사용자 또는 로그에 인쇄하려는 경우 "xyz로 인해 login()이 실패했습니다."또는 "xyz로 인해 sendmail()이 실패했습니다."라는 메시지는 동일한 예외가 발생할 수 있기 때문에 불가능합니다. 나는 로깅 목적을 위해 무엇이 잘못되었는지에 대한 상세한 처리를 원한다. – Tom

+0

예외는 세부 사항을 제공 할 수 있어야합니다. 예 : "gaierror : except gaierror :"대신 "gaierror, except (code, message) :"를 사용할 수 있습니다. 그런 다음 오류 코드와 오류 메시지가 표시되며 자세한 오류 처리를 위해 오류 코드와 오류 메시지를 사용할 수 있습니다. if code == 11001 : print "unknown host name :", 메시지 – Ralph

+0

나는 당신이 말하려고하는 것을 완전히 얻지 못했다고 생각합니다. 다음을 시도하십시오 : SMTP 객체로 만든 다음 연결하지 않고 smtp.login()을 시도한 다음 연결하지 않고 smtp.sendmail()을 시도하면 100 % 동일한 예외가 발생하고 msg와 에 의해 errno – Tom

3

.. 뭔가를 보일 수 있습니다 , 초기 연결이 이루어지면 서버 연결이 제대로 정리됩니다. , 그들이 성공하면 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

나에게 직관적 인 것처럼 보입니다. 그리고 이것은 아마도 "java"에서와 같이 보일 것입니다. 당신은 또한 python2.5 iirc 전에 이것을 가지고 있지 않았다. 그것은 큰 try 블록을 피하기 위해 문서에서 소개되었지만 그룹화를 멋지고 명료하게 유지하므로 함께 속한 모든 코드는 try ... except ... else 블록에서 try 부분을 날려 보내지 않고 계속됩니다. proprtions. pythonians가 스타일 가이드와 peps에 너무 붙어 있기 때문에, 나는 이것이 올바른 길일 것이라고 생각했습니다. – 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." 

를, "자기, 당신은 꽤 하드 명령 패턴을 다하고 만들지 않고 Command 클래스. 어쩌면 지금은 시간이다."

관련 문제