assert
デバッグ目的でのみ使用するのではなく、標準コードの一部として使用することで、パフォーマンスまたはコードのメンテナンスの問題がありますか?は
assert x >= 0, 'x is less than zero'
より良いまたはより悪い
if x < 0: raise Exception, 'x is less than zero'
また、そのようなビジネスルールを設定する方法はありますか?それ
if x < 0 raise error
なしで常にチェックtry/except/finally
されます。コード全体でいつでもx
0未満の場合assert x < 0
、関数の開始時に設定した場合のように、関数内の任意の場所でエラーが発生します。どこx
が0未満になると、例外が発生しますか?
15 に答える
アサートは、決して起こらない条件をテストするために使用する必要があります。目的は、プログラムの状態が破損した場合に早期にクラッシュすることです。
例外は、発生する可能性のあるエラーに使用する必要があり、ほとんどの場合、独自のExceptionクラスを作成する必要があります。
たとえば、構成ファイルからを読み取る関数を作成している場合dict
、ファイルのフォーマットが不適切な場合は、が発生するはずですが、を返すことはConfigurationSyntaxError
できassert
ませんNone
。
あなたの例でx
は、がユーザーインターフェイスまたは外部ソースから設定された値である場合、例外が最適です。
x
同じプログラム内の独自のコードによってのみが設定されている場合は、アサーションを使用します。
コンパイルが最適化されると、「assert」ステートメントは削除されます。つまり、はい、パフォーマンスと機能の両方の違いがあります。
現在のコードジェネレーターは、コンパイル時に最適化が要求されたときに、assertステートメントのコードを出力しません。-Python2ドキュメント Python3ドキュメント
を使用assert
してアプリケーション機能を実装し、本番環境へのデプロイメントを最適化すると、「but-it-works-in-dev」の欠陥に悩まされます。
PYTHONOPTIMIZEおよび-O-OOを参照してください
関数全体でxがゼロ未満になったときに自動的にエラーをスローできるようにするため。クラス記述子を使用できます。次に例を示します。
class LessThanZeroException(Exception):
pass
class variable(object):
def __init__(self, value=0):
self.__x = value
def __set__(self, obj, value):
if value < 0:
raise LessThanZeroException('x is less than zero')
self.__x = value
def __get__(self, obj, objType):
return self.__x
class MyClass(object):
x = variable()
>>> m = MyClass()
>>> m.x = 10
>>> m.x -= 20
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "my.py", line 7, in __set__
raise LessThanZeroException('x is less than zero')
LessThanZeroException: x is less than zero
の4つの目的assert
4人の同僚のAlice、Bernd、Carl、およびDaphneと一緒に200,000行のコードで作業していると仮定します。彼らはあなたのコードを呼び、あなたは彼らのコードを呼びます。
次に、 4つの役割assert
があります。
コードが期待することをAlice、Bernd、Carl、およびDaphneに通知します。
タプルのリストを処理するメソッドがあり、それらのタプルが不変でない場合、プログラムロジックが壊れることがあると仮定します。def mymethod(listOfTuples): assert(all(type(tp)==tuple for tp in listOfTuples))
これは、ドキュメント内の同等の情報よりも信頼性が高く、保守がはるかに簡単です。
コードが期待することをコンピューターに通知します。
assert
コードの呼び出し元から適切な動作を強制します。あなたのコードがAlicesを呼び出し、Berndのコードがあなたのコードを呼び出すassert
場合、プログラムがAlicesコードでクラッシュした場合、BerndはそれがAliceのせいであると見なす可能性があり、Aliceは調査し、それがあなたのせいであると見なす可能性があります。彼の。多くの仕事が失われました。
アサーションを使用すると、電話を間違えた人は誰でも、それがあなたのせいではなく、自分のせいであることがすぐにわかります。アリス、ベルント、そしてあなた方全員が恩恵を受けます。時間を大幅に節約します。あなたのコード(あなた自身を含む)の読者にあなたのコードがある時点で何を達成したかを知らせてください。
エントリのリストがあり、それぞれがクリーン(これは良い)であるか、スモーシュ、トラル、ガルアップ、またはきらめき(すべて受け入れられない)であると仮定します。それがスモーシュである場合、それはスモーシュされていない必要があります。それがtraleである場合、それはbaludoedされなければなりません。ガラップの場合は、トロットする必要があります(そして、場合によってはペースも調整する必要があります)。きらめく場合は、木曜日を除いて再びきらめく必要があります。あなたは考えを理解します:それは複雑なものです。しかし、最終的な結果は、すべてのエントリがクリーンであるということです(またはそうあるべきです)。行うべき正しいこと(TM)は、クリーニングループの効果を次のように要約することです。assert(all(entry.isClean() for entry in mylist))
このステートメントは、すばらしいループが達成していることが正確に何であるかを理解しようとするすべての人の頭痛の種を救います。そして、これらの人々の中で最も頻繁なのはおそらくあなた自身でしょう。
ある時点でコードが達成したことをコンピューターに通知します。
速歩した後にそれを必要とするエントリのペースを忘れた場合、それassert
はあなたの日を節約し、あなたのコードがずっと後に親愛なるダフネのコードを壊すことを避けます。
私の考えでassert
は、ドキュメンテーションの2つの目的(1と3)とセーフガード(2と4)は等しく価値があります。
人々に知らせることは、コンピュータに知らせることよりもさらに価値があるかもしれません。なぜなら、それは、assert
捕らえることを目的とするまさに間違い(ケース1)と、どんな場合でもその後の多くの間違いを防ぐことができるからです。
他の回答に加えて、アサート自体は例外をスローしますが、AssertionErrorsのみです。功利主義の観点から、アサーションは、キャッチする例外をきめ細かく制御する必要がある場合には適していません。
このアプローチで本当に間違っているのは、assertステートメントを使用して非常に説明的な例外を作成するのが難しいことだけです。より単純な構文を探している場合は、次のようなこともできることを覚えておいてください。
class XLessThanZeroException(Exception):
pass
def CheckX(x):
if x < 0:
raise XLessThanZeroException()
def foo(x):
CheckX(x)
#do stuff here
もう1つの問題は、通常の状態チェックにassertを使用すると、-Oフラグを使用してデバッグアサートを無効にすることが困難になることです。
ここで主張する英語の単語は、誓う、肯定する、誓うという意味で使用されます。「チェック」や「すべき」という意味ではありません。これは、コーダーとしてのあなたがここで宣誓供述をしていることを意味します。
# I solemnly swear that here I will tell the truth, the whole truth,
# and nothing but the truth, under pains and penalties of perjury, so help me FSM
assert answer == 42
コードが正しければ、シングルイベントアップセットやハードウェア障害などを除いて、アサートが失敗することはありません。そのため、エンドユーザーに対するプログラムの動作に影響を与えてはなりません。特に、例外的なプログラム条件の下でもアサーションが失敗することはありません。それは決して起こりません。それが起こった場合、プログラマーはそれのためにザッピングされるべきです。
前に述べたように、コードがポイントに到達してはならない場合、つまりバグがある場合は、アサーションを使用する必要があります。おそらく、アサーションを使用するために私が見ることができる最も有用な理由は、不変/前/後の条件です。これらは、ループまたは関数の各反復の開始時または終了時に真でなければならないものです。
たとえば、再帰関数(2つの別々の関数で、1つは不正な入力を処理し、もう1つは不正なコードを処理するため、再帰との区別が難しくなります)。これにより、ifステートメントを書くのを忘れた場合、何がうまくいかなかったのかが明らかになります。
def SumToN(n):
if n <= 0:
raise ValueError, "N must be greater than or equal to 0"
else:
return RecursiveSum(n)
def RecursiveSum(n):
#precondition: n >= 0
assert(n >= 0)
if n == 0:
return 0
return RecursiveSum(n - 1) + n
#postcondition: returned sum of 1 to n
これらのループ不変条件は、多くの場合、アサーションで表すことができます。
さて、これは未解決の質問です。私が触れたい2つの側面があります。それは、アサーションを追加するタイミングと、エラーメッセージの記述方法です。
目的
初心者に説明すると、アサーションはエラーを引き起こす可能性のあるステートメントですが、キャッチすることはできません。そして、通常は育てるべきではありませんが、実際にはとにかく育てられることがあります。そして、これはコードが回復できない深刻な状況であり、いわゆる「致命的なエラー」です。
次に、これは「デバッグ目的」のためのものであり、正しいとはいえ、非常に否定的に聞こえます。私は「不変条件を宣言するのが好きです。これは決して違反してはならない」定式化ですが、初心者によっては動作が異なります。またはそれでフローを制御します。
スタイル
Pythonでは、これassert
はステートメントであり、関数ではありません。(レイズしないことを忘れassert(False, 'is true')
ないでください。しかし、それを邪魔にならないようにすること:
オプションの「エラーメッセージ」をいつ、どのように書き込むのですか?
assertTrue(condition)
これは、アサーション(assertFalse(condition), assertEqual(actual, expected)
など)を実行するための多くの専用メソッドを備えていることが多い単体テストフレームワークに正確に適用されます。また、アサーションについてコメントする方法も提供することがよくあります。
使い捨てコードでは、エラーメッセージなしで実行できます。
場合によっては、アサーションに追加するものがありません。
def dump(something):assert isinstance(something、Dumpable)#..。
ただし、それとは別に、メッセージは他のプログラマー(Ipython / Jupyterなどのコードのインタラクティブユーザーである場合もあります)との通信に役立ちます。
内部実装の詳細を漏らすだけでなく、情報を提供します。
それ以外の:
assert meaningless_identifier <= MAGIC_NUMBER_XXX, 'meaningless_identifier is greater than MAGIC_NUMBER_XXX!!!'
書きます:
assert meaningless_identifier > MAGIC_NUMBER_XXX, 'reactor temperature above critical threshold'
または多分:
assert meaningless_identifier > MAGIC_NUMBER_XXX, f'reactor temperature({meaningless_identifier }) above critical threshold ({MAGIC_NUMBER_XXX})'
私は知っています、私は知っています-これは静的アサーションの場合ではありませんが、メッセージの情報価値を指摘したいと思います。
否定的または肯定的なメッセージ?
これは物議を醸すかもしれませんが、次のようなものを読むのは私を傷つけます:
assert a == b, 'a is not equal to b'
これらは、隣り合って書かれた2つの矛盾したものです。したがって、コードベースに影響を与えるときはいつでも、「must」や「should」などの追加の動詞を使用して、必要なものを指定するようにプッシュします。不要なものは言わないでください。
a == bをアサートし、'aはbと等しくなければなりません'
次に、gettingAssertionError: a must be equal to b
も読み取り可能であり、ステートメントはコード内で論理的に見えます。また、トレースバックを読み取らずに何かを取得することもできます(トレースバックが利用できない場合もあります)。
パフォーマンスの問題はありますか?
「高速に動作させる前に、まず動作させる」ことを忘れないでください。
通常、プログラムのごくわずかな割合がその速度に関連しています。パフォーマンスの問題であることが判明した場合は、いつでもキックアウトまたは単純化できassert
ます。ほとんどの場合、そうなることはありません。実用的である:
空でないタプルのリストを処理するメソッドがあり、それらのタプルが不変でない場合、プログラムロジックが壊れるとします。あなたは書くべきです:def mymethod(listOfTuples): assert(all(type(tp)==tuple for tp in listOfTuples))
リストが10エントリの長さになる傾向がある場合、これはおそらく問題ありませんが、100万エントリがある場合は問題になる可能性があります。しかし、この貴重なチェックを完全に破棄するのではなく、単にダウングレードすることができます
def mymethod(listOfTuples): assert(type(listOfTuples[0])==tuple) # in fact _all_ must be tuples!
これは安価ですが、とにかく実際のプログラムエラーのほとんどをキャッチする可能性があります。
assert
価値があるのは、正しく機能するために依存するコードを扱っている場合、次のコードを追加することで、アサートが有効になっていることを確認します。
try:
assert False
raise Exception('Python assertions are not working. This tool relies on Python assertions to do its job. Possible causes are running with the "-O" flag or running a precompiled (".pyo" or ".pyc") module.')
except AssertionError:
pass
アサーションはチェックすることです-1
。有効な条件、
2。有効なステートメント、
3。真のロジック。
ソースコードの。プロジェクト全体を失敗させる代わりに、ソースファイルに何かが適切でないことを警告します。
例1では、変数'str'はnullではないためです。したがって、アサートや例外は発生しません。
例1:
#!/usr/bin/python
str = 'hello Python!'
strNull = 'string is Null'
if __debug__:
if not str: raise AssertionError(strNull)
print str
if __debug__:
print 'FileName '.ljust(30,'.'),(__name__)
print 'FilePath '.ljust(30,'.'),(__file__)
------------------------------------------------------
Output:
hello Python!
FileName ..................... hello
FilePath ..................... C:/Python\hello.py
例2では、var'str'はnullです。そのため、 assertステートメントによってユーザーが障害のあるプログラムに先んじることを防いでいます。
例2:
#!/usr/bin/python
str = ''
strNull = 'NULL String'
if __debug__:
if not str: raise AssertionError(strNull)
print str
if __debug__:
print 'FileName '.ljust(30,'.'),(__name__)
print 'FilePath '.ljust(30,'.'),(__file__)
------------------------------------------------------
Output:
AssertionError: NULL String
デバッグを望まず、ソースコードのアサーションの問題に気付いた瞬間。最適化フラグを無効にする
python-OassertStatement.py
何も出力されません
assert
例外の使用と発生はどちらもコミュニケーションに関するものです。
アサーションは、開発者が対象とするコードの正確性に関するステートメントです。コード内のアサーションは、コードが正しいために満たす必要のある条件について、コードのリーダーに通知します。実行時に失敗するアサーションは、修正が必要なコードに欠陥があることを開発者に通知します。
例外は、実行時に発生する可能性があるが、そこで処理される呼び出し元のコードで対処される手元のコードでは解決できない、非典型的な状況に関する兆候です。例外の発生は、コードにバグがあることを示すものではありません。
ベストプラクティス
したがって、実行時に特定の状況が発生したことを、開発者に通知したいバグと見なす場合(「開発者の皆さん、この状態はどこかにバグがあることを示しています。コードを修正してください。」)。アサーションに行きます。アサーションがコードの入力引数をチェックする場合、通常、入力引数がその条件に違反すると、コードに「未定義動作」があることをドキュメントに追加する必要があります。
代わりに、そのような状況の発生が目のバグの兆候ではなく、代わりに(おそらくまれですが)クライアントコードによって処理されるべきであると思われる可能性のある状況である場合は、例外を発生させます。例外が発生する状況は、それぞれのコードのドキュメントの一部である必要があります。
使用にパフォーマンスの問題がありますか[...]
assert
アサーションの評価には時間がかかります。ただし、コンパイル時に削除することができます。これにはいくつかの結果がありますが、以下を参照してください。
使用に[...]コードのメンテナンスの問題がありますか
assert
通常、アサーションは、仮定を明示的にし、実行時にこれらの仮定を定期的に検証することで読みやすさを向上させるため、コードの保守性を向上させます。これは、リグレッションのキャッチにも役立ちます。ただし、覚えておく必要のある問題が1つあります。それは、アサーションで使用される式に副作用がないことです。上記のように、アサーションはコンパイル時に削除できます。つまり、潜在的な副作用もなくなります。これにより、意図せずにコードの動作が変わる可能性があります。
PTVS、PyCharmなどのIDEでは、Wingassert isinstance()
ステートメントを使用して、一部の不明瞭なオブジェクトのコード補完を有効にすることができます。
フォーマル検証済みのソフトウェアで指定するのと同じように、ループ不変条件assert
やコードに必要な論理プロパティなどのプロパティを指定するためによく使用します。
それらは、読者に情報を提供すること、私が推論するのを助けること、そして私が私の推論を間違えていないことを確認することの両方の目的を果たします。例えば :
k = 0
for i in range(n):
assert k == i * (i + 1) // 2
k += i
#do some things
またはより複雑な状況では:
def sorted(l):
return all(l1 <= l2 for l1, l2 in zip(l, l[1:]))
def mergesort(l):
if len(l) < 2: #python 3.10 will have match - case for this instead of checking length
return l
k = len(l // 2)
l1 = mergesort(l[:k])
l2 = mergesort(l[k:])
assert sorted(l1) # here the asserts allow me to explicit what properties my code should have
assert sorted(l2) # I expect them to be disabled in a production build
return merge(l1, l2)
Pythonを最適化モードで実行するとアサートが無効になるため、特にコードがより明確になり、バグが発生しにくくなる場合は、コストのかかる条件を遠慮なく記述してください。