python-マルチディスク-エラー/デバッグ/テスト

10326 ワード

エラー処理


try...except...finally...
try:
    print('try...')      #  try except finally  
    r = 10 / int('a')
    print('result:', r)
except ValueError as e:
    print('ValueError:', e)
except ZeroDivisionError as e:
    print('ZeroDivisionError:', e)
finally:
    print('finally...')
print('END')

呼び出しスタック


エラーがキャプチャされていない場合は、常に上に投げ出され、最後にPython解釈器にキャプチャされ、エラーメッセージが印刷され、プログラムが終了します.err.pyを見てみましょう
# err.py:
def foo(s):
    return 10 / int(s)

def bar(s):
    return foo(s) * 2

def main():
    bar('0')

main()

実行結果は次のとおりです.
$ python3 err.py
Traceback (most recent call last):
  File "err.py", line 11, in 
    main()
  File "err.py", line 9, in main
    bar('0')
  File "err.py", line 6, in bar
    return foo(s) * 2
  File "err.py", line 3, in foo
    return 10 / int(s)
ZeroDivisionError: division by zero

間違いは怖くない.恐ろしいのはどこが間違っているのか分からないことだ.エラー情報を解読することは、エラーを特定する鍵です.エラーの呼び出し関数チェーン全体が上から下に表示されます.
エラーメッセージ1行目:
Traceback (most recent call last):

これは誤った追跡情報であることを教えてください.2~3行目:
  File "err.py", line 11, in 
    main()

呼び出しmain()でエラーが発生しました.コードファイルerr.pyの11行目のコードですが、原因は9行目です.
............省略する上流は汚染して下流はすべて汚染して、すべてPM爆発表、システムはすべての汚染地区をすべて展示して、あなたは上流に沿って行って、汚染源を触ることができます............
原因はreturn foo(s)*2という文が間違っていますが、これは最終的な原因ではありません.続けて下を見てください.
  File "err.py", line 3, in foo
    return 10 / int(s)

原因はreturn 10 / int(s)という文が間違っています.これはエラーの発生源です.以下に印刷されているからです.
ZeroDivisionError: integer division or modulo by zero

エラータイプZeroDivisionErrorによれば、int(s)自体にエラーはないと判断したが、int(s)は0を返し、10/0の計算でエラーが発生し、エラーの元が見つかった.

ロギングエラー


エラーをキャプチャしなければ、Python解釈器にエラースタックを印刷させることは自然にできますが、プログラムも終了します.エラーをキャプチャできる以上、エラースタックを印刷し、エラーの原因を分析しながら、プログラムを実行し続けることができます.
Pythonに内蔵されたロゴモジュールは、エラー情報を簡単に記録できます.
# err_logging.py

import logging

def foo(s):
    return 10 / int(s)

def bar(s):
    return foo(s) * 2

def main():
    try:
        bar('0')
    except Exception as e:
        logging.exception(e)

main()
print('END')

同じエラーですが、プログラムはエラーメッセージを印刷した後も実行を続け、正常に終了します.
$ python3 err_logging.py
ERROR:root:division by zero
Traceback (most recent call last):
  File "err_logging.py", line 13, in main
    bar('0')
  File "err_logging.py", line 9, in bar
    return foo(s) * 2
  File "err_logging.py", line 6, in foo
    return 10 / int(s)
ZeroDivisionError: division by zero
END

構成により、loggingはエラーをログファイルに記録することができ、後で確認するのに便利です.

エラーを投げ出す


エラーはclassであるため、エラーをキャプチャすることはclassにキャプチャされたインスタンスです.したがって、エラーは空で発生するのではなく、意図的に作成され、投げ出されます.Pythonの内蔵関数は多くのタイプのエラーを投げ出し、私たちが自分で書いた関数もエラーを投げ出すことができます.
エラーを投げ出す場合は、まず必要に応じて、エラーのclassを定義し、継承関係を選択し、raise文でエラーのインスタンスを投げ出すことができます.
# err_raise.py
class FooError(ValueError):
    pass

def foo(s):
    n = int(s)
    if n==0:
        raise FooError('invalid value: %s' % s)
    return 10 / n

foo('0')

実行すると、自分たちが定義したエラーを最後に追跡できます.
$ python3 err_raise.py 
Traceback (most recent call last):
  File "err_throw.py", line 11, in 
    foo('0')
  File "err_throw.py", line 8, in foo
    raise FooError('invalid value: %s' % s)
__main__.FooError: invalid value: 0

必要なときだけ、私たち自身のエラータイプを定義します.Pythonの既存の内蔵エラータイプ(ValueError,TypeErrorなど)を選択できる場合は、Python内蔵エラータイプをできるだけ使用します.最後に、別のエラー処理方法を見てみましょう.
# err_reraise.py

def foo(s):
    n = int(s)
    if n==0:
        raise ValueError('invalid value: %s' % s)
    return 10 / n

def bar():
    try:
        foo('0')
    except ValueError as e:
        print('ValueError!')
        raise

bar()
bar()関数では、エラーをキャプチャしたのに、ValueError!を印刷した後、raise文を通じてエラーを投げ出してしまいました.これは病気ではありませんか.
実はこのような誤った処理方法は病気ではないだけでなく、かなりよく見られます.エラーをキャプチャする目的は記録するだけで、後続の追跡に便利です.しかし,現在の関数ではこのエラーをどのように処理すべきか分からないため,最上位呼び出し者に処理させ続けるのが最も適切である.例えば、従業員が一つの問題を処理できないときは、問題を彼のボスに投げて、もし彼のボスも処理できないならば、ずっと上に投げて、最終的にCEOに投げて処理します.raise文はパラメータを持たないと、現在のエラーをそのまま投げ出します.また、exceptのうちraiseの1つのErrorにおいて、1つのタイプのエラーを別のタイプに変換することもできる.

デバッグ


断言する

print()で閲覧を支援する場所は、(assert)と断言することができます.
def foo(s):
    n = int(s)
    assert n != 0, 'n is zero!'
    return 10 / n

def main():
    foo('0')

assertの意味は、式n!=0はTrueのはずです.そうしないと、プログラムが実行する論理によって、後のコードがエラーになるに違いありません.
断言に失敗すると、assert文自体はAssertionErrorを放出します.
$ python3 err.py
Traceback (most recent call last):
  ...
AssertionError: n is zero!

logging

print()loggingに置き換えるのは3つ目の方法で、assertと比べて、loggingはエラーを投げ出すことなく、ファイルに出力することができます.
import logging

s = '0'
n = int(s)
logging.info('n = %d' % n)
print(10 / n)
logging.info()でテキストを出力できます.実行して、ZeroDivisionErrorを除いて、何の情報もありません.どうしたの?
焦らないで、import loggingの後に1行の構成を追加してから試してください.
import logging
logging.basicConfig(level=logging.INFO)

出力が表示されます.
$ python3 err.py
INFO:root:n = 0
Traceback (most recent call last):
  File "err.py", line 8, in 
    print(10 / n)
ZeroDivisionError: division by zero

これがloggingのメリットです.記録情報のレベルを指定できます.debug、info、warning、errorなどいくつかのレベルがあります.level=INFOを指定すると、logging.debugは機能しません.同様に、level=WARNINGを指定すると、debugとinfoは機能しません.これにより、異なるレベルの情報を安心して出力したり、削除したりせずに、どのレベルの情報を出力するかを統一的に制御することができます.
loggingのもう一つの利点は、簡単な構成によって、consoleやファイルなどの異なる場所に同時に出力できることです.

pdb pdb.set_trace()IDE暫定


ユニットテスト


「テスト駆動開発」(TDD:Test-Driven Development)ユニットテストは、モジュール、関数、またはクラスの正確性検査に使用されるテスト作業です.
次のように、dictと一致する動作を持つDictクラスを作成します.しかし、プロパティでアクセスできます.
>>> d = Dict(a=1, b=2)
>>> d['a']
1
>>> d.a
1

ユニットテストを記述するには、Pythonが付属するunittestモジュールを導入する必要があります.mydict_test.pyを記述するには、次のようにします.
import unittest

from mydict import Dict

class TestDict(unittest.TestCase):

    def test_init(self):
        d = Dict(a=1, b='test')
        self.assertEqual(d.a, 1)
        self.assertEqual(d.b, 'test')
        self.assertTrue(isinstance(d, dict))

    def test_key(self):
        d = Dict()
        d['key'] = 'value'
        self.assertEqual(d.key, 'value')

    def test_attr(self):
        d = Dict()
        d.key = 'value'
        self.assertTrue('key' in d)
        self.assertEqual(d['key'], 'value')

    def test_keyerror(self):
        d = Dict()
        with self.assertRaises(KeyError):
            value = d['empty']

    def test_attrerror(self):
        d = Dict()
        with self.assertRaises(AttributeError):
            value = d.empty

ユニットテストを作成する場合は、unittest.TestCaseから継承されるテストクラスを作成する必要があります.testで始まる方法が試験方法であり、testで始まる方法は試験方法とみなされず、試験時に実行されない.
各クラスのテストにはtest_xxx()メソッドを記述する必要があります.unittest.TestCaseは多くの組み込み条件判断を提供するため、これらの方法を呼び出すだけで出力が私たちが望んでいるかどうかを断言することができます.最もよく使われる断言はassertEqual()です.
self.assertEqual(abs(-1), 1) #  1 

もう1つの重要な断言は、存在しないkeyにd['empty']でアクセスすると、keyErrorが放出されるなど、指定されたタイプのErrorを期待することです.
with self.assertRaises(KeyError):
    value = d['empty']
d.emptyを介して存在しないkeyにアクセスする場合、AttributeErrorを放出することを期待します.
with self.assertRaises(AttributeError):
    value = d.empty

ユニットテストの実行


ユニットテストを作成すると、ユニットテストを実行できます.最も簡単な動作は、mydict_test.pyの最後に2行のコードを追加することです.
if __name__ == '__main__':
    unittest.main()

これにより、mydict_test.pyを通常のpythonスクリプトとして実行できます.
$ python3 mydict_test.py

もう1つの方法は、コマンドラインでパラメータ-m unittestを介してユニットテストを直接実行することである.
$ python3 -m unittest mydict_test
.....
----------------------------------------------------------------------
Ran 5 tests in 0.000s

OK

これは、多くのユニットテストを一括して実行でき、これらのユニットテストを自動的に実行できるツールがたくさんあるため、推奨されています.

setUpとtearDown


2つの特別なsetUp()およびtearDown()の方法は、ユニット試験において記述することができる.この2つのメソッドは、1つのテストメソッドが呼び出されるたびにそれぞれ実行されます.setUp()tearDown()の方法は何の役に立ちますか?テストでデータベースを起動する必要がある場合、setUp()メソッドでデータベースに接続し、tearDown()メソッドでデータベースを閉じることができます.これにより、各テストメソッドで同じコードを繰り返す必要がなくなります.
class TestDict(unittest.TestCase):

    def setUp(self):
        print('setUp...')

    def tearDown(self):
        print('tearDown...')

テストを再実行して、各テストメソッド呼び出しの前後にsetUpが印刷されるかどうかを確認できます.とtearDown....

ドキュメントテスト


Pythonの公式ドキュメントをよく読むと、多くのドキュメントにサンプルコードが表示されます.例えば、reモジュールには多くのサンプルコードがあります.
>>> import re
>>> m = re.search('(?<=abc)def', 'abcdef')
>>> m.group(0)
'def'


これらのサンプルコードはPythonのインタラクティブな環境で入力して実行することができ、結果はドキュメントのサンプルコードの表示と一致します.
これらのコードは、他の説明とコメントに書くことができ、いくつかのツールによってドキュメントを自動的に生成します.これらのコード自体が貼り付けられて直接実行できる以上、コメントに書かれたこれらのコードを自動的に実行してもいいですか?
答えは肯定的だ.
コメントを作成するときに、次のようなコメントを書きます.
def abs(n):
    '''
    Function to get absolute value of number.

    Example:

    >>> abs(1)
    1
    >>> abs(-1)
    1
    >>> abs(0)
    0
    '''
    return n if n >= 0 else (-n)


関数の呼び出し者に、関数の所望の入力と出力をより明確に伝えるに違いない.
また、Pythonに内蔵されている「ドキュメントテスト」(doctest)モジュールは、コメントのコードを直接抽出してテストを実行することができます.