2009-06-12 20 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の前提条件となります。

私はまた、適切なリソースのクリーンアップに興味があります。でも、Pythonがそれを管理することさえできます。

ありがとう、Tom

+1

dbrを編集していただきありがとうございますが、あなた自身についてはわからないことを編集しないでください。編集基準を基準に編集しましたが、実際には単数で、編集した箇所に複数の意味がないので、 – Tom

+0

Opps、申し訳ありませんが、私は今までの基準の一つしか聞いたことがないと思っています。 – dbr

答えて

24

代わりの試みを使用して/ elseブロックだ除いて、あなたは単に時にエラーを返すことができます:

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

完璧に読めるとPython的だこれ...

これを行うための別の方法ではなく心配よりも、あります具体的な実装について、次にmessage()方法、CAのコードを書く

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

..あなたは、あなたのコードは、例えば、見てみたい方法を決定予想どおりのエラーが発生した場合、独自のカスタムエラーを発生させて、それが適切な場所で処理します。成功した場合に

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つの例外しか使用しません」という問題を解決したのは本当に好きです。 –

+1

最初の例では、send_message()は常にserver.login()の後に戻り、メッセージを送信しません。私は最終的にこの声明のためにすべきだとは思わない。 – mhawke

+1

今、それは原則の問題に沸きます。あなたの最初のコードは基本的に私と同じですが、Pythonのドキュメントで示唆された "else"ツリーで行ったように例外を入れ子にしていないだけです。どちらが良い練習ですか? docsは、tryブロック内の追加の文ではなく、常にelseを優先すべきであると述べています。それは基本的に同じ問題です。if:条件付きでifを入れ子にする方が良い場合、別のものの前に戻る方が良いでしょうか。 – Tom

0

どうして大した試みはしませんか?このようにして、例外が検出された場合は、例外のすべてに行きます。また、異なるステップのすべての例外が異なる限り、例外を発生させた部分はいつでもわかります。

+0

大きなtryブロックがあなたにressourcesの世話をすることはないでしょう。あなたがリソースが実際に割り当てられているかどうかわからないので、文は2例外をスローします。実際にどのステートメントが実際に失敗したかわからないため、後でエラーが出ることはありません。 – 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 

、我々はserver.login smtplib.SMTP()、()、およびserver.sendmail()すべてがフラット化する別の例外をスローするという事実を使用しますtry-catchブロックのツリー。 finallyブロックでは、サーバーを明示的にテストして、nilオブジェクトに対してquit()を呼び出さないようにします。個別に処理する必要が重なって、例外ケースがある場合

我々はまた、例外条件にFalseを返す、3 シーケンシャルのtry-catchブロックを使用することができます。

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は同じ例外をスローする可能性があります(「サーバーに最初に接続」) しかし、上記のように、私はこの特定の問題。私はこれを解決する一般的なアプローチにもっと興味があります。 2番目のアプローチは、基本的には「else」がないコードです。私は認めなければなりません。) – Tom

+1

finallyブロックに注意してください。存在しない変数を間違って参照しないようにするには、ブロックの前にNoneを設定します。 –

+0

@Tom +1だから、私はこの解決策を提案しなかったのです。 – Unknown

0

私はDavidの答えが気に入っていますが、サーバの例外についていない場合は、サーバがNoneか状態であるかどうかを確認することもできます。私はこの方法を少し平らにしていますが、それはまだではありませんが、見た目には見劣りしていませんが、下のロジックではより読みやすくなります。

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を返し、状態をローカル変数に保存する代わりにブロックします。 – Tom

1

ただ1つのtry-blockを使用するだけです。これはまさに彼らが のために設計されたものです:前の ステートメントが例外をスローしなかった場合にのみ次のステートメントを実行します。リソースのクリーンアップについては、 リソースをクリーンアップする必要がある場合は、 (たとえば、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つの異なる関数呼び出し、例えばloginとsendmailが同じ例外をスローする可能性があるため、エラーメッセージはあいまいであるためです。ユーザーまたはログに印刷する場合は、両方の呼び出しが同じ例外を発生させる可能性があるため、 "xyzのためログインに失敗しました"または "xyzが原因でsendmail()が失敗しました"私は、ロギング目的で何が問題になったのかを詳細に処理したい。 – Tom

+0

例外はその詳細を提供できるはずです。例えば。単純な "except gaierror:"の代わりに "gaierror以外(code、message):"を使うことができます。次に、エラーコードとエラーメッセージが表示され、詳細なエラー処理に使用できます。 if code == 11001:print "unknown host name:"、メッセージ – Ralph

+0

あなたは私が言っていることを完全に得られなかったと思います。次のことを試してみてください:自分自身をSMTPオブジェクトにして、接続せずにsmtp.login()を試してから、smtp.sendmail()を接続せずに試してみましょう。同じ例外を100%スローします。 〜によって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ブロック内にあります。 proprtions。 pythoniansがスタイルガイドとpepsにとても繋がっているので、私はこれが正しい方法であると考えました。 – Tom

1

私はこのような何かをしようとするだろう彼らはそれをトラップしません。このプロトコルはまた、メソッドが例外を発生させることなく失敗したことを示す必要がある場合を処理することも可能にします。 (あなたのメソッドが失敗する唯一の方法は、例外を発生させることであり、それはプロトコルを単純化します。もし失敗したメソッドの外に例外ではない多くの失敗状態に対処しなければならない場合、 )まだ実現していません。

このアプローチの欠点は、すべてのメソッドが同じ引数を使用しなければならないことです。私は何も選択しませんでしたが、私が外したメソッドがクラスメンバーを操作することになるとの期待をもっています。

しかし、この手法の利点はかなりあります。まず、send_messageが複雑にならずに、数十のメソッドをプロセスに追加できます。

また夢中になると、このような何か行うことができます。その時点で、私は自己」、自分自身に言うかもしれませんが、あなたが作成しなくても、かなりハードCommandパターンを作業している、...

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クラス。今は時でしょう。"

関連する問題