web-dev-qa-db-ja.com

pythonでのエラーと成功の戻り値のベストプラクティス

generalでは、次のようなメソッドがあるとしましょう。

def intersect_two_lists(self, list1, list2):
    if not list1:
        self.trap_error("union_two_lists: list1 must not be empty.")
        return False
    if not list2:
        self.trap_error("union_two_lists: list2 must not be empty.")
        return False
    #http://bytes.com/topic/python/answers/19083-standard
    return filter(lambda x:x in list1,list2)

エラーが見つかったこの特定のメソッドでは、この特定のメソッド呼び出しに対する本当の答えであった可能性があるため、この場合は空のリストを返したくありません。パラメータが正しくなかったことを示す何かを返します。したがって、この場合はエラー時にFalseを返し、それ以外の場合は空のリストを返しました。

私の質問は、リストだけでなく、このような分野でのベストプラクティスは何ですか?私が望むものは何でも返して、ユーザーが読むことができるように文書化してください? :-)あなたのほとんどの人々は何をしますか:

  1. 成功すると、TrueまたはFalseを返すことになっていて、エラーをキャッチしますか?
  2. 成功すると、リストを返すことになっていて、エラーをキャッチしますか?
  3. 成功すると、ファイルハンドルを返すことになっていて、エラーをキャッチしますか?
  4. など
48
TallPaul

まず、結果とエラーメッセージを返さない場合。これはエラーを処理するための非常に悪い方法であり、無限の頭痛の種になります。 エラーを示す必要がある場合は常に例外を発生させます

私は通常、必要でない限りエラーの発生を避ける傾向があります。あなたの例では、エラーを投げることは本当に必要ありません。空のリストを空でないリストと交差させることはエラーではありません。結果は単なる空のリストであり、それは正しいです。しかし、他のケースを処理したいとしましょう。たとえば、メソッドがリスト以外の型を取得した場合。この場合、例外を発生させることをお勧めします。例外は恐れることではありません。

あなたへの私のアドバイスは、Python同様の関数のライブラリを見て、Pythonがこれらの特殊なケースをどのように処理するかを確認することです。ここでは、空のセットと空のリストを交差させようとしています。

>>> b = []
>>> a = set()
>>> a.intersection(b)
set([])

>>> b = [1, 2]
>>> a = set([1, 3])
>>> a.intersection(b)
set([1])

エラーは必要な場合にのみスローされます:

>>> b = 1
>>> a.intersection(b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable

もちろん、成功または失敗したときにTrueまたはFalseを返すことが適切な場合があります。しかし、一貫性を保つことは非常に重要です。関数は常に同じ型または構造を返す必要があります。リストまたはブール値を返す可能性のある関数があると、非常に混乱します。または、同じ型を返しますが、エラーの場合、この値の意味は異なる場合があります。

編集:

OPは言う:

パラメーターが正しくなかったことを示すために何かを返したいです。

例外よりも優れたエラーがあるということは何もありません。パラメータが正しくないことを示したい場合は、例外を使用して有用なエラーメッセージを入力してください。この場合、結果を返すのは混乱を招くだけです。何も起きていないがエラーではないことを示したい場合があります。たとえば、テーブルからエントリを削除するメソッドがあり、削除が要求されたエントリが存在しない場合。この場合、成功または失敗したときにTrueまたはFalseを返すだけでよい場合があります。それはアプリケーションと意図された動作に依存します

59
Nadia Alramli

例外を発生させる の方が特別な値を返すよりも良いでしょう。これは、エラーコードをより堅牢で構造化されたエラー処理メカニズムに置き換えるために設計された例外です。

class IntersectException(Exception):
    def __init__(self, msg):
        self.msg = msg
    def __str__(self):
        return self.msg

def intersect_two_lists(self, list1, list2):
    if not list1: raise IntersectException("list1 must not be empty.")
    if not list2: raise IntersectException("list2 must not be empty.")

    #http://bytes.com/topic/python/answers/19083-standard
    return filter(lambda x:x in list1,list2)

この特定のケースでは、おそらくテストをやめるだけでしょう。空のリストが交差しても問題はありません。また、lambdaは、リスト内包表記よりも最近では推奨されていません。 lambdaを使用せずにこれを記述する方法については、 2つのリストの共通部分を検索しますか? を参照してください。

21
John Kugelman

タプルを返すのが好きです:

(True、some_result)

(False、some_useful_response)

some_useful_responseオブジェクトは、戻り条件の処理に使用したり、デバッグ情報の表示に使用したりできます。

[〜#〜] note [〜#〜]:この手法は、あらゆる種類の戻り値に適用されます。 例外の場合と間違えないでください。

受信側では、開梱するだけです。

コード、応答= some_function(...)

この手法は、「通常の」制御フローに適用されます。予期しない入力/処理が発生した場合、例外機能を使用する必要があります。

注目に値する:この手法は、関数の戻り値を正規化するのに役立ちます。プログラマーと関数のユーザーの両方期待することを知っている

免責事項:私はアーランのバックグラウンドから来ました:-)

13
jldupont

例外は、ステータスが返されるよりも間違いなく優れています(さらにPython的な)。これに関する詳細: 例外とステータスの戻り値

11
Ned Batchelder

一般的なケースは、例外的な状況で例外をスローすることです。正確な引用(または誰が言ったのか)を覚えておいてほしいのですが、合理的な数の値と型を受け入れ、非常に厳密に定義された動作を維持する関数を探す必要があります。これは、 ナディアが話していた の変形です。関数の次の使用法を考慮してください。

  1. intersect_two_lists(None, None)
  2. intersect_two_lists([], ())
  3. intersect_two_lists('12', '23')
  4. intersect_two_lists([1, 2], {1: 'one', 2: 'two'})
  5. intersect_two_lists(False, [1])
  6. intersect_two_lists(None, [1])

Falseを渡すことは型エラーであるため、(5)は例外をスローすると予想されます。ただし、それらの残りの部分は何らかの意味をなしますが、実際には関数が記述するコントラクトに依存します。 intersect_two_listsは、2つの iterables の共通部分を返すものとして定義されていたため、None空のセット。実装は次のようになります。

def intersect_two_lists(seq1, seq2):
    if seq1 is None: seq1 = []
    if seq2 is None: seq2 = []
    if not isinstance(seq1, collections.Iterable):
        raise TypeError("seq1 is not Iterable")
    if not isinstance(seq2, collections.Iterable):
        raise TypeError("seq1 is not Iterable")
    return filter(...)

私は通常、契約が何であれ実施するヘルパー関数を作成し、それらを呼び出してすべての前提条件をチェックします。何かのようなもの:

def require_iterable(name, arg):
    """Returns an iterable representation of arg or raises an exception."""
    if arg is not None:
        if not isinstance(arg, collections.Iterable):
            raise TypeError(name + " is not Iterable")
        return arg
    return []

def intersect_two_lists(seq1, seq2):
    list1 = require_iterable("seq1", seq1)
    list2 = require_iterable("seq2", seq2)
    return filter(...)

この概念を拡張し、オプションの引数として "policy"を渡すこともできます。 Policy Based Design を受け入れたくない限り、これを行うことはお勧めしません。以前にこのオプションを検討したことがない場合に備えて、言及したかったのです。

intersect_two_listsは、2つの空でないlistパラメーターのみを受け入れ、明示的であり、契約に違反した場合に例外をスローすることです。

def require_non_empty_list(name, var):
    if not isinstance(var, list):
        raise TypeError(name + " is not a list")
    if var == []:
        raise ValueError(name + " is empty")

def intersect_two_lists(list1, list2):
    require_non_empty_list('list1', list1)
    require_non_empty_list('list2', list2)
    return filter(...)

物語の教訓は、あなたが何をするにしても、一貫してそれを行い、明示的であると思います。個人的に、私は通常、契約に違反したとき、または本当に使用できない値が与えられたときはいつでも例外を上げることを好みます。与えられた値が合理的であれば、見返りに合理的なことをしようとします。また、例外について C++ FAQ Liteエントリ を読むこともできます。この特定のエントリは、思考のためのより多くの食物を提供します例外について。

3
D.Shawley

コレクション(リスト、セット、辞書など)の場合、空のコレクションを返すことは明らかな選択です。これは、呼び出しサイトロジックが防御ロジックを排除できるようにするためです。より明確に、空のコレクションは、コレクションを期待する関数からの完全に良い答えであり、結果が他のタイプのものであることを確認する必要がなく、ビジネスロジックをクリーンな方法で継続できます。

コレクション以外の結果の場合、条件付きリターンを処理する方法がいくつかあります。

  1. 多くの答えがすでに説明したように、例外を使用することはこれを解決するための1つの方法であり、慣用的なPythonです。ただし、制御フローに例外を使用しないのが私の好みです。例外の意図があいまいになるためです。代わりに、実際の例外的な状況で例外を発生させます。
  2. 別の解決策は、予想される結果の代わりにNoneを返すことですが、これによりユーザーはコールサイトのあらゆる場所に防御チェックを追加し、実際のビジネスロジックを難読化します彼らは実際に実行しようとしています。
  3. 3番目の方法は、単一の要素のみを保持できるコレクションタイプを使用することです(または明示的に空)。これはOptionalと呼ばれ、呼び出しサイトのロジックをきれいに保つことができるため、私の推奨方法です。ただし、pythonにはオプションの型が組み込まれていないため、独自の型を使用します。 という小さなライブラリに公開しましたoptional.py 試してみたい人はpip install optional.pyを使用してインストールできます。コメント、機能のリクエスト、貢献を歓迎します。
1
Chad Befus