2

ターゲット関数で発生したエラーをキャッチし、ユーザーがスクリプトの実行を続行する (関数をバイパスする) か、スクリプトからドロップアウトできるようにするデコレータを作成しています。

def catch_error(func):
    """
    This decorator is used to make sure that if a decorated function breaks 
    in the execution of a script, the script doesn't automatically crash. 
    Instead, it gives you the choice to continue or gracefully exit.    

    """
    def caught(*args):
        try:
            return func(*args)
        except Exception as err:
            question = '\n{0} failed. Continue? (yes/no): '.format(func.func_name)
            answer = raw_input(question)
            if answer.lower() in ['yes','y']:
                pass
            else:
                print "   Aborting! Error that caused failure:\n"
                raise err 
            return None
    return caught

ユーザーがエラーを返す関数をバイパスしてスクリプトの実行を続行することを選択した場合、デコレータは None を返すことに注意してください。これは、単一の値のみを返す関数ではうまく機能しますが、複数の値をアンパックしようとする関数ではクラッシュします。例えば、

# Both function and decorator return single value, so works fine
one_val = decorator_works_for_this_func() 
# Function nominally returns two values, but decorator only one, so this breaks script
one_val, two_val = decorator_doesnt_work_for_this_func()

ターゲット関数が返す値の数を特定する方法はありますか? たとえば、次のようなものです。

def better_catch_error(func):
    def caught(*args):
        try:
            return func(*args)
        except Exception as err:
            ...
            num_rvals = determine_num_rvals(func)
            if num_rvals > 1:
                return [ None for count in range(num_rvals) ]
            else:
                return None               
    return caught

いつものように、この種のことを行うためのより良い方法があれば、私に知らせてください. ありがとう!

アップデート:

すべての提案をありがとう。私は、catch_error の範囲を、1 つの文字列値のみを返す単一クラスの関数に絞り込むことにしました。複数の値を返すすべての関数を、単一の値を返す個別の関数に分割して、互換性を持たせました。私は catch_error をより一般的なものにしたいと考えていました (そして、その方法についていくつかの役立つ提案がありました) が、私のアプリケーションにとっては少しやり過ぎでした。再度、感謝します。

4

3 に答える 3

3

デコレータは関数が何を返すかを予測できませんが、シミュレートする戻りシグネチャをデコレータに指示することを妨げるものは何もありません。

@catch_error([None, None])
def tuple_returner(n):
    raise Exception
    return [2, 3]

を返す代わりにNone、デコレータは引数([None, None])を返します。

引数を取るデコレータを書くのは少し注意が必要です。式catch_error([None, None])が評価され、デコレートされた関数に適用される実際のデコレータを返す必要があります。次のようになります。

def catch_error(signature=None):
    def _decorator(func):
        def caught(*args):
            try:
                return func(*args)
            except Exception as err:
                # Interactive code suppressed
                return signature

        return caught

    return _decorator

単に返したい場合でも、None一度実行する必要があることに注意してください。

@catch_error()
def some_function(x):
    ...
于 2013-01-30T01:09:05.753 に答える
3

Martijn Pieters の答えは正しいです。これは停止問題の特定のケースです。

ただし、エラーの戻り値をデコレータに渡すことで回避できる場合があります。このようなもの:

def catch_error(err_val):
    def wrapper(func):
        def caught(*args):
            try:
                return func(*args)
            except Exception as err:
                question = '\n{0} failed. Continue? (yes/no): '.format(func.func_name)
                answer = raw_input(question)
                if answer.lower() in ['yes','y']:
                    pass
                else:
                    print "   Aborting! Error that caused failure:\n"
                    raise err 
                return err_val
        return caught
    return wrapper

次に、次を使用して装飾できます。

@catch_error({})
def returns_a_dict(*args, **kwargs):
    return {'a': 'foo', 'b': 'bar'}

また、スタイルのポイントとして、ラップされた関数をグラブする場合は、キーワード引数を取る関数を適切にラップできるように、*argsおそらくグラブも行う必要があります。**kwargsそうしないと、ラップされた関数は失敗しますwrapped_function(something=value)

最後に、スタイルのもう 1 つのポイントとして、else を使用するコードを見ると混乱しif a: passます。このような場合に使用if !aしてみてください。したがって、最終的なコードは次のとおりです。

def catch_error(err_val):
    def wrapper(func):
        def caught(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except Exception as err:
                question = '\n{0} failed. Continue? (yes/no): '.format(func.func_name)
                answer = raw_input(question)
                if answer.lower() not in ['yes','y']:
                    print "   Aborting! Error that caused failure:\n"
                    raise err
                return err_val
        return caught
    return wrapper
于 2013-01-30T00:40:21.457 に答える
3

いいえ、それを判断する方法はありません。

Python は動的言語であり、指定された関数は、入力やその他の要因に基づいて、任意のサイズの任意のシーケンスを返すことも、シーケンスをまったく返さないこともあります。

例えば:

import random

def foo():
    if random.random() < 0.5:
        return ('spam', 'eggs')
    return None

この関数は半分の時間でタプルを返しますがNone、残りの半分であるため、Python には何foo()が返されるかを伝える方法がありません。

関数が呼び出し元がアンパックするシーケンスを返す場合だけでなく、デコレータが失敗する可能性がある方法は他にもたくさんあります。呼び出し元が辞書を期待してキーにアクセスしようとした場合、またはリストにアクセスしようとして長さを知りたい場合はどうなるでしょうか?

于 2013-01-30T00:01:48.753 に答える