例外の処理は良い習慣ですが、すべての場合にtry-exceptツリーを記述すると、これらの構造が繰り返され始め、コードが複雑になり読みにくくなります。 また、怠のすべての処理を記述します。
そのため、例外処理に苦痛はありません。また、コードの再現性を減らすには、Pythonのパワーと柔軟性を使用する必要があります。
はじめに
一般に、予防策を講じてすべてを事前に確認しても、遅かれ早かれプログラムの例外が発生します。
たとえば、いくつかのアクション(1つのフォルダーからの取得、別のフォルダーへのコピー、3番目のフォルダーの作成、4番目のファイルへのリンク)を行うサービススクリプト。 事前にすべてをチェックするようにスマートにすることができます:ファイルがあるかどうか、それらへのアクセスがあるかどうかなど。 ただし、これはアクション自体が実行されることをまったく保証しません。チェック後、ディスクがいっぱいにならない、切断されない、またはファイルが消去されないということです。 遅かれ早かれ、このような状況が発生し、プログラムが適切に抜け出すことができた方が良いでしょう。
有効なエラーメッセージは、トレースよりも常に優れています。 見てみましょう:
import os os .listdir( dir )
import os os .listdir( dir )
トレースバック(最後の最後の呼び出し): ファイル「/tmp/test.py」、12行目、<module> os.listdir(dir) OSError:[Errno 13]許可が拒否されました: '/ root'
ユーザーには、理解できない単語トレースバックと多くの不要な情報が表示されます(チェーンに沿ってN個の関数が呼び出された場合、トレースはN倍長くなります)。
基本的に、プログラム全体をtryでラップし、例外をキャッチして、何かを書くことができます。
- import os 、 shutil 、 sys
- 試してください :
- os .listdir( '/ etc' )
- シャティル 。 コピー ( '/home/culebron/test.txt' 、 '/ etc' )
- os .listdir( '/ root' )
- f = open ( '/ etc / hosts' 、 'a' )
- f.write( ')
- (OSError、IOError)を除く:
- print 'エラー#{1 [1] [0]}:{1 [1] [1]}'。format(dir、sys.exc_info())
エラー#13:許可が拒否されました
(初心者向けの注意:例外を除いて、提供されたタイプのみをインターセプトし、残りを抑制しないために、例外のタイプを指定することを強くお勧めします。)
これでメッセージは読み取り可能になりましたが、何をどこで破損したかは理解できませんが、メッセージが正確である場合(たとえば、「/ rootフォルダーを読み取れません」)、有能なユーザーは問題が何であるかを理解できます。
上記の宿題:コードツリーの助けを借りて、どのアクションが機能しなかったか、どのエラーが発生したかが明確になるようにします。 たとえば、次のようになります(言語は関係ありません):
/ルートフォルダーを読み取れません。 間違い13:許可が拒否された
最も単純な標準ハンドラー
便利なプログラムのように見える単純なスクリプトを次に示します。 / etc / hostsファイルに新しい行を書き込み、その内容を表示します。
- #!/ usr / bin / python
- myfile = '/ etc / hosts'
- 試してください :
- open (myfile、 'a' ) を f として :
- f.write( '127.0.0.1 anotherhost' )
- 例外 ( OSError 、 IOError ):
- sys .exit( 'ファイル.format(args []、sys.exc_info()[1] .args)の操作中にエラーが発生しました)
- 試してください:
- fとして open(myfile、 ' a ')を使用:
- print ' ' .join(f)
- (OSError、IOError)を除く:
- sys.exit( ' .formatファイルの操作中にエラーが発生しました(args []、 sys .exc_info()[ 1 ] .args))
明らかに、コードは繰り返され、その多くは一般的なメソッドに持ち込むことができます。 自動例外処理のためのすべてのメソッドを記述するモジュールを作成しましょう。 ここでは、失敗の場合に単に停止するコンソールスクリプトの教育目的でのみ例を示します。
- #!/ usr / bin / python
- インポートシステム
- def fopen( * args):
- 試してください :
- 戻り値 ( * args)
- 例外 ( OSError 、 IOError ):
- sys .exit( '.formatファイルを開くときのエラー(args []、sys.exc_info()[1] .args))
ファイルを安全な名前で保存し、作業スクリプトからロードします。
- #!/ usr / bin / python
- 安全な輸入
- myfile = '/ etc / hosts'
- safe.fopen(myfile、 'a' ) を f として :
- a.write( '127.0.0.1 anotherhost' )
- safe.fopen(myfile、 'r' ) を f として :
- 印刷 '' .join(f)
これで、sudoを使用せずにスクリプトを実行すると、スクリプトが美しく停止します。 もちろん、書き込みまたは読み取り中に既に障害が発生している場合は、インターセプトされませんが、後でそのような場合のために保護します。
幅広い用途の方法
そのため、open関数を独自のものに置き換えましたが、実際のタスクでは、さらに多くの関数を操作する必要があります-
os.listdir、os.chmod、os.chown、shutil.copy、shutil.mv、shutil.mkdir、shutil.makedirsなど。 独自の理解可能なエラーメッセージが必要です。
2つの解決策があります。
例外を処理し、指定されたメソッドを呼び出すメソッドを作成します
メソッドを置き換える(つまり、既存のメソッドを装飾する)
単純なものから複雑なものまで:最初に、最初のメソッド、指定されたメソッドを呼び出すメソッドを試してください。
safe.pyモジュールで、メソッドを作成します
- def catch(メソッド、引数、メッセージ、 例外 =( OSError 、 IOError )):
- isinstanceで ない 場合 (args、( list 、 tuple )):
- args = [引数]
- 試してください :
- リターンメソッド( * args)
- 例外 を 除く :
- quit(message.format( * args)+ '.format(sys.exc_info()[1] .args)、1)
これで、任意の関数を呼び出してエラーを処理し、エラーメッセージのみを表示できます。
- safe.catch( os .listdir、 '/ root' 、 '{0}を開けません' )
- safe.catch( shutil。copy 、(pathA、pathB)、 '{0}を{1}にコピーできません' )
この種類は適用できますが、アクション自体(os.listdir)は行の先頭ではなく括弧で囲まれているため、読みにくくなります。
2番目の方法を試してみましょう-メソッドを置き換える、つまり、装飾します。 最初のメソッドcatchが必要になります。キー引数を使用して関数を呼び出し、catchを呼び出すラッパーを実行するデコレータを作成できるように、少し書き換えます。
- def catch(メソッド、メッセージ、 例外 、 * args、 ** kwargs):
- isinstanceで ない 場合 (メッセージ、 str ):
- TypeErrorを発生させます ( 「メッセージテキストは文字列でなければなりません」 )
- 試してください :
- リターンメソッド( * args)
- 例外 を 除く :
- sys .exit(message.format( * args)+ '+' 、 '.join(sys.exc_info()[1] .args)、1)
- def wrap(メソッド、メッセージ、例外=(IOError、OSError)):
- def fn(*引数、** kwargs):
- return catch(メソッド、メッセージ、例外、* args、** kwargs)
- fnを返す
- open = wrap(open、 ' Can)
これで、safeモジュールには、標準のopenに代わるopenメソッドがあり、他のメソッドはラップでラップできます。 このメソッドは、独自の関数のデコレーターとしても使用できます。
多くの場合、ユーティリティスクリプトを記述するとき、あるスクリプトの一部を別のスクリプトで使用する必要があることがわかります。 したがって、それらをすぐにモジュールとして再利用し、少なくともすべての作業アクションをメソッドに入れ、グローバルコードにサービスアクション(たとえば、コマンドラインパラメーターの処理)を残すことを期待する方が良いです。
- #!/ usr / bin / python
- import os 、 shutil 、safe、 sys
- hosts_file = '/ etc / hosts'
- @ safe.wrap( 'ファイルの読み取りエラー{0}' )
- def writehost(ip、host):
- 安全で。 f として (hosts_file、 'a' ) を 開きます :
- f.write( '.format(ip、host))
- __name__ == ' __main__ 'の場合:
- writehost(sys.argv [1:])
- safe.open(hosts_file、 ' r ')をfとして:
- print f.read()
ここで、実際には、キャッチされない例外が存在する可能性があります-これはファイルを読み取り中です(最終行)。 使用可能なツールで処理するには、アクションをメソッドに入れる必要がありますが、これを行いたくないと考えて(小さなメソッドの群れもオプションではありません)、try-exceptコンストラクトを記述します。 メッセージを表示する既存のコードを繰り返さないために、別のダンプ関数で安全なモジュールに入れてください:
(safe.py)
- def catch(メソッド、メッセージ、 例外 、 * args、 ** kwargs):
- isinstanceで ない 場合 (メッセージ、 str ):
- TypeErrorを発生させます ( 「メッセージテキストは文字列でなければなりません」 )
- 試してください :
- リターンメソッド( * args)
- 例外 を 除く :
- 終了(メッセージ、 * args)
- def wrap(メソッド、メッセージ、 例外 =( IOError 、 OSError )):
- def fn( * args、 ** kwargs):
- return catch(メソッド、メッセージ、 例外 、 * args、 ** kwargs)
- fnを返す
- open = wrap( open 、 'Can)
- def quit(msg、* args):
- sys.exit(msg.format(* args)+ ' Error: '、 ' 、 ' .join(map(str、sys.exc_info()[1] .args))、1)
- #すべての引数が文字列ではなく、str関数によって変換される必要がある
(メインプログラム)
- ...
- 安全で。 f として (hosts_file、 'r' ) を 開きます :
- 試してください :
- 印刷 '' .join(f)
- IOError を除く :
- safe.quit( 「ファイル{0}は開かれましたが、読み取りに失敗しました。」 、hosts_file)
- ...
(注:プログラムとセーフモジュールでは、デコレータを使用してデータ型を確認しています。)
次は?
まず、例外トレースには、Pythonが出力するものよりもはるかに多くの有用な情報が含まれています。 ユーザーが開発者に送信できるように、このデータをファイルのどこかに保存するメソッドを作成すると便利です。
まとめ
多くの典型的なケースでは、例外を処理する4つの方法があります。
アクションを新しいメソッドに移動し、wrapメソッドで装飾します
インラインメソッドをラップで装飾する
catch例外ハンドラーを介してメソッドを呼び出す
try-exceptコンストラクトをコードで記述し、quitメソッドを呼び出します
...そして、人件費は最小限です。ハンドラーの名前とエラーメッセージのテキストを記述するだけです。 例外的な例外は非常にまれであり、それらについてはtry-exceptを記述します。これによりコードが複雑になることはありません。