Pythonアサートのベストプラクティス


483
  1. assertデバッグ目的でのみ使用するのではなく、標準コードの一部として使用すると、パフォーマンスまたはコードのメンテナンスの問題がありますか?

    です

    assert x >= 0, 'x is less than zero'

    より良いか悪いか

    if x < 0:
        raise Exception, 'x is less than zero'
  2. また、if x < 0 raise errorそのように常にチェックされるようなビジネスルールを設定する方法はありますか。try/except/finallyコード全体でいつでもx0未満の場合assert x < 0、関数の開始時、関数内のどこかに設定した場合のように、エラーが発生します。どこxが0未満になると、例外が発生しますか?



29
-Oおよび-OOのpythonパラメーターは、アサーションを取り除きます。それは何のために良いのかについてあなたの考えを駆り立てるはずです。
Peter Lada

4
Thomasz Zielinskiのリンクが壊れてしまいました。現在は、mail.python.org / pipermail / python-list / 2013-November / 660568.htmlです。pipermailには不安定なID関数があると確信しています。同じ意図で同じURLを指している同じpipermail内から他のリンクを見つけました。
quodlibetor 2016

3
mail.python.org/pipermail/python-list/2013-November/660568.htmlが再び移動した場合、archive.is / 5GfiGにアーカイブされます。投稿のタイトルは「アサートを使用するタイミング」で、Pythonのベストプラクティスに関する優れた投稿(本当に記事)ですassert
2016

回答:


144

関数全体で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

10
プロパティは記述子として実装されていますが、これを使用例とは呼びません。これは、プロパティ自体の例です: docs.python.org/library/functions.html#property
Jason Baker

3
プロパティは、xを設定するときにMyClass内で使用する必要があります。このソリューションは一般的すぎます。

113
いい答えですが、質問に対しては何もしないでください... DeestanまたはJohn Meeの答えを有効な応答としてマークできませんか?
Vajk Hermecz 2013

4
これは質問のタイトルに答えていないようです。また、これはPythonのクラスプロパティ機能の代替としては不十分です。
Dooms101 2014

10
@VajkHermecz:実際、質問をもう一度読んだ場合、これは2つの質問を1つにまとめたものです。タイトルだけを見ている人は最初の質問に精通しているだけで、この答えは答えません。この回答には、実際には2番目の質問に対する回答が含まれています。
ArtOfWarfare 2015年

742

アサートは、発生しはならない条件をテストするために使用する必要があります。目的は、プログラム状態が破損した場合に早期にクラッシュすることです。

例外は、おそらく発生する可能性のあるエラーに使用する必要があり、ほとんどの場合、独自のExceptionクラスを作成する必要があります


たとえば、構成ファイルからに読み込む関数を作成している場合、ファイル内のdict不適切な書式設定によりConfigurationSyntaxErrorが発生assertしますが、戻りたくない場合がありますNone


あなたの例でxは、がユーザーインターフェイスまたは外部ソースから設定された値である場合、例外が最適です。

x同じプログラム内の独自のコードによってのみ設定される場合は、アサーションを使用します。


126
これはアサートを使用する正しい方法です。プログラムフローの制御に使用しないでください。
Thane Brimhallが2012年

41
+1は、最後の段落のために-あなたがすべきであるのに、明示的に言及することはassert、暗黙的な含まれていif __debug__てもよく、最適化された離れて-とジョン・ミーの答えの状態
トビアスKienzler

3
答えをもう一度読んでください。あなたがたまたま原則として意味するべきではない状況を意味しているのではないでしょう。しかし、目的は、通常は予期しない状況と一致する破損したプログラム状態の場合、早期にクラッシュすることです。これまでに起こる
Bentley4 2014年

10
assertは、既知の回復がない問題を検出するためにのみ使用してください。ほとんどの場合、バグをコード化します(悪い入力ではありません)。アサートがトリガーされた場合、プログラムがネットワークとの通信またはディスクへの書き込みを開始する可能性があるため、プログラムの続行が危険な状態にある可能性があることを意味します。堅牢なコードは、不正な(または悪意のある)入力に直面しても、有効な状態から有効な状態に「自動的に」移動します。すべてのスレッドの最上位には障害バリアが必要です。外界からの入力を消費する障害バリアは、通常、バリアの1回の反復(試行中/試行中)で失敗し、ロールバック/エラーでログオンします。
Rob

10
「アサートは、発生してはならない条件をテストするために使用されるべきです。」はい。そして、2番目の「すべき」の意味は次のとおりです。これが発生した場合、プログラムコードは正しくありません。
Lutz Prechelt 2014

362

「assert」ステートメントは、コンパイルが最適化されると削除されます。したがって、はい、パフォーマンスと機能の両方に違いがあります。

現在のコードジェネレーターは、コンパイル時に最適化が要求されたときに、assertステートメントのコードを発行しません。- Pythonの2ドキュメント のPython 3ドキュメント

を使用assertしてアプリケーション機能を実装し、本番環境へのデプロイメントを最適化すると、「but-it-works-in-dev」の欠陥に悩まされます。

PYTHONOPTIMIZEおよび-O -​​OOを参照してください


26
うわー!それは超重要なメモです!私はアサートを使用して失敗してはならないいくつかのことをチェックすることを計画していました。それは機能しませんが、アサートを使用して彼らの試みを迅速にシャットダウンしたいので、それを本番環境で最適化すると目的が無効になります。私raiseException代わりに私がやると思います。ああ-私SuspiciousOperation Exceptionはサブクラスで適切に命名されたDjango!パーフェクト!
ArtOfWarfare 2014

ちなみに、@ ArtOfWarfareをbanditコードで実行すると、警告が表示されます。
Nagev

132

の4つの目的 assert

4人の同僚Alice、Bernd、Carl、およびDaphneと一緒に200,000行のコードで作業しているとします。彼らはあなたのコードを呼び、あなたは彼らのコードを呼びます。

次にassert4つの役割があります

  1. コードが期待することをアリス、ベルント、カール、ダフネに通知します。
    タプルのリストを処理するメソッドがあり、それらのタプルが不変でない場合、プログラムのロジックが壊れると想定します。

    def mymethod(listOfTuples):
        assert(all(type(tp)==tuple for tp in listOfTuples))

    これは、ドキュメント内の同等の情報よりも信頼性が高く、保守がはるかに簡単です。

  2. コードが期待することをコンピューターに通知します。
    assertコードの呼び出し元からの適切な動作を強制します。コードがAlicesを呼び出し、Berndのコードがあなたを呼び出すassert場合、なしで、プログラムがAlicesコードでクラッシュした場合、BerndはそれがAliceの責任であると見なし、Aliceが調査し、それがあなたの責任であると見なす可能性があります。調査し、Berndに実際に連絡した彼。多くの仕事が失われました。
    アサートを使用すると、呼び出しが間違っている人は誰でも、それが自分のせいではなく、自分のせいであることがすぐにわかります。アリス、ベルント、そしてあなた方全員が恩恵を受けます。時間を大幅に節約できます。

  3. コードの読者(自分を含む)に、コードがある時点で達成したことを知らせます。
    エントリーのリストがあり、それぞれがクリーン(これは良い)であるか、それがスモッシュ、トレル、ガルアップ、またはトゥインクルド(いずれも許容されない)であると想定します。それがスモッシュである場合、それはスモッシュされていないに違いない。それが奇妙なものであるならば、それは脱毛されなければならない。ガルアップの場合は、トロットする必要があります(そして、ペースを合わせてください)きらめく場合は、木曜日を除いてもう一度きらめく必要があります。あなたはアイデアを得ます:それは複雑なものです。しかし、最終結果はすべてのエントリがクリーンである(またはそうあるはずです)です。やるべきことは、クリーニングループの効果を次のようにまとめることです。

    assert(all(entry.isClean() for entry in mylist))

    このステートメントは、すばらしいループが実現していることを正確に理解しようとするすべての人の頭痛を軽減します。そして、これらの人々の中で最も頻繁なのはおそらくあなた自身でしょう。

  4. ある時点でコードが達成したことをコンピューターに通知します。
    トロッティング後にそれを必要とするエントリのペースを設定することを忘れた場合でも、これによりassert1日が節約され、コードがダフネの大分遅れて壊れることがなくなります。

私の考えでは、assertの2つの目的(1と3)と保護手段(2と4)は同様に価値があります。
人々に通知することは、コンピュータに通知することよりも価値があるかもしれません。なぜなら、それにより、assert目的(1の場合)を捕まえることを目的とする非常に間違いを防ぐことができます。


34
5. isinstance()アサートして、PyCharm(python IDE)が変数のタイプを知るのを助けます。オートコンプリートに使用されます。
Cjkjvfnby 2014年

1
現在の実行時に正しいことについて、自己文書化されたコードの仮定を表明します。チェックされる仮定コメントです。
pyj 2014年

9
2と4に関しては、アサートが厳しすぎないように十分に注意してください。そうでなければ、アサート自体が、プログラムをより一般的な設定で使用できるようにする唯一の方法かもしれません。特に型のアサーションは、Pythonのダックタイピングに反します。
zwirbeltier 2015年

9
@Cjkjvfnbyこのブログエントリで説明されているように、isinstance()の使いすぎに注意してください:「isinstance()は有害と見なされます」。あなたは今できるタイプを指定するには、ドキュメンテーション文字列を使用し Pycharmに。
binarysubstrate 2015年

2
契約を保証する1つの方法でアサートを使用する。Design by Contractに関する詳細en.wikipedia.org/wiki/Design_by_contract
Leszek Zarna

22

他の回答に加えて、アサート自体は例外をスローしますが、AssertionErrorsのみをスローします。実用的な観点から見ると、アサーションは、どの例外をキャッチするかを細かく制御する必要がある場合には適していません。


3
正しい。呼び出し側でアサーションエラー例外をキャッチするのはばかげているように思えます。
Raffi Khatchadourian、2011年

非常に良い点です。マクロレベルから元の質問を見るだけで見過ごされがちなニュアンス。最適化時にアサーションが削除されるという問題ではなかったとしても、発生したエラーの種類の特定の詳細を失うと、デバッグがさらに困難になります。乾杯、outis!
cfwschmidt 2017

あなたの答えはAssertionErrors、それを大まかにしても大丈夫なときに、あなたがキャッチしたいかもしれないかのように読むことができます。実際には、それらをキャッチするべきではありません。
Tomasz Gandor

19

このアプローチで本当に間違っている唯一のことは、assertステートメントを使用して非常に記述的な例外を作ることが難しいことです。より単純な構文を探している場合は、次のようなことも実行できることを覚えておいてください。

class XLessThanZeroException(Exception):
    pass

def CheckX(x):
    if x < 0:
        raise XLessThanZeroException()

def foo(x):
    CheckX(x)
    #do stuff here

別の問題は、通常の条件チェックにアサートを使用すると、-Oフラグを使用してデバッグアサートを無効にすることが困難になることです。


24
アサーションにエラーメッセージを追加できます。2番目のパラメーターです。それはそれを説明的にします。
Raffi Khatchadourian、2011年

10

英語の単語のassertここでは、の意味で使用されて誓うAFFIRM言い放ちます「チェック」またはすべき」という意味ではありません。それは、あなたがコーダーとしてここに誓った声明を出していることを意味します

# 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

コードが正しい場合、シングルイベントの混乱、ハードウェア障害などがなければ、アサートは失敗しません。これが、エンドユーザーに対するプログラムの動作に影響を与えてはならない理由です。特に、プログラムの例外的な状況下でも、アサートは失敗しません。それは決して起こりません。それが起こった場合、プログラマーはそれに打ちのめされるべきです。


8

以前に述べたように、アサーションは、コードがポイントに到達してはならないときに、つまりそこにバグがあるときに使用する必要があります。おそらく、アサーションを使用するために私が見ることができる最も有用な理由は、不変条件/事前/事後条件です。これらは、ループまたは関数の各反復の開始時または終了時に真でなければならないものです。

たとえば、再帰関数(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
これはデコレータ(@preconditionと@postcondition)を使用して行うのが最適です
Caridorc

@Caridorcその具体的なメリットは何ですか?
Chiel ten Brinke、2016

@ChieltenBrinke自己文書化コード、#precondition: n >= 0 アサートの代わりに、彼はただ書くことができます@precondition(lambda n: n >= 0)
Caridorc

@Caridorcそれらの組み込みのデコレータはその時ですか?そして、それからどのようにしてドキュメントを生成しますか?
Chiel ten Brinke、2016

@ChieltenBrinkeは組み込みではありませんが、簡単に実装できますstackoverflow.com/questions/12151182/…。ドキュメンテーションについては__doc__、追加の文字列を指定して属性にパッチを適用してください
Caridorc

4

パフォーマンスの問題ありますか?

  • 「早く動作させる前に、最初に動作させる」ことを忘れないでください。
    通常、プログラムの数パーセントがその速度に関係しています。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!

    これは安価ですが、実際のプログラムエラーのほとんどをとにかくキャッチします。


2
する必要がありますassert(len(listOfTuples)==0 or type(listOfTyples[0])==tuple)
osa 14

いいえ、できません。2番目のアサートがチェックする「空でない」プロパティをチェックしないため、これははるかに弱いテストになります。(最初のものはそうではありませんが、そうではありません。)
Lutz Prechelt

1
2番目のアサートは、空でないプロパティを明示的にチェックしません。それは副作用のより多くです。リストが空であるために例外が発生した場合、コードを操作する人(他のユーザーまたは作成者、コードを記述してから1年後)はそれをじっと見て、アサーションが本当にキャッチするつもりだったかどうかを調べようとします空のリストの状況、またはそれがアサート自体のエラーである場合。さらに、最初の要素のチェックだけが「97%正しい」のに対して、空のケースをチェックしないことが「はるかに弱い」のはわかりません。
osa 2015年

3

さて、これは未解決の質問であり、私が触れておきたい2つの側面があります。アサーションを追加するタイミングとエラーメッセージを書き込む方法です。

目的

初心者に説明するには-アサーションはエラーを発生させる可能性があるステートメントですが、キャッチすることはできません。そして、彼らは通常、育てられるべきではありませんが、実際には、とにかく育てられることがあります。そして、これはコードが回復できない深刻な状況であり、「致命的エラー」と呼ばれています。

次に、それは「デバッグ目的」のためのものであり、正しいのですが、非常に否定的です。初心者によって動作が異なりますが、「違反しないでください」という変則を宣言する定式化が好きです...「理解するだけ」もあれば、用途が見つからないものや、通常の例外を置き換えるものもあります。またはそれでフローを制御します。

スタイル

Pythonでは、assertはステートメントではなく、関数です!(assert(False, 'is true')レイズしないことを忘れないでください。しかし、それを邪魔にならないようにしてください:

いつ、どのように、オプションの「エラーメッセージ」を書き込むのですか?

これはacuallyしばしば(アサーションを行うには、多くの専用のメソッドを持っているユニットテストフレームワークに適用される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つの矛盾したものです。したがって、コードベースに影響を与えるときはいつでも、「必須」や「推奨」などの追加の動詞を使用して、必要なものを指定せず、不要なことは言わないようにします。

    a == bをアサートします、「aはbと等しい必要があります」

次に、getting AssertionError: a must be equal to bも読み取り可能で、ステートメントはコード内で論理的に見えます。また、トレースバックを読み込まなくても、何かを取得できます(トレースバックを利用できない場合もあります)。


1

assert例外の使用と発生の両方は、コミュニケーションに関するものです。

  • アサーションは、開発者が対処するコードの正確性に関するステートメントです。コード内のアサーションは、コードが正しいために満たす必要のある条件についてコードの読者に通知します。実行時に失敗するアサーションは、修正が必要なコードに欠陥があることを開発者に通知します。

  • 例外は、実行時に発生する可能性があるが、手元のコードでは解決できない非典型的な状況についての指示であり、そこで処理される呼び出しコードで対処されます。例外の発生は、コードにバグがあることを示すものではありません。

ベストプラクティス

したがって、実行時に特定の状況の発生を、開発者に通知したいバグと見なす場合(「開発者様、この状態は、どこかにバグがあることを示しています。コードを修正してください。」)アサーションに行きます。アサーションがコードの入力引数をチェックする場合、通常、入力引数がその条件に違反している場合、コードに「未定義の動作」があることをドキュメントに追加する必要があります。

そのような状況の発生が目のバグの兆候ではなく、クライアントコードで処理する必要があると考えられる(たぶんまれですが)可能性のある状況の場合は、例外を発生させます。例外が発生する状況は、それぞれのコードのドキュメントの一部である必要があります。

使用時にパフォーマンスの問題がありますか[...] assert

アサーションの評価には時間がかかります。ただし、コンパイル時に削除できます。これにはいくつかの影響がありますが、以下を参照してください。

使用に伴う[...]コードのメンテナンスの問題はありますか assert

通常、アサーションは、仮定を明示的にすることにより、また実行時にこれらの仮定を定期的に検証することにより、可読性を向上させるため、コードの保守性を向上させます。これは、リグレッションの把握にも役立ちます。ただし、注意が必要な問題が1つあります。アサーションで使用される式には副作用があってはなりません。上記のように、アサーションはコンパイル時に削除できます。つまり、潜在的な副作用も消えます。これにより、意図せずにコードの動作が変更される可能性があります。


1

アサートはチェックすることです-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 -O assertStatement.py
何も印刷されません


0

PTVS、PyCharm、IDEなどのIDEでは、Wing assert isinstance()ステートメントを使用して、不明なオブジェクトのコード補完を有効にすることができます。


これは、型注釈またはの使用に先行するようですtyping.cast
Acumenus

-1

何に価値があるかについては、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

2
これは、ベストプラクティスに関するOPの質問には答えません。
codeforester 2018年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.