38

このコードを Python で記述するより良い方法はありますか?

result = slow_function()
if result:
    return result
[...]

関数slow_functionは値を返すことができます orNone遅いので、これは実行できません:

if slow_function():
    return slow_function()

最初の方法に問題はありませんが、一時変数を使用することは Python にとってやり過ぎのようです。

このコードは、再帰呼び出しを使用してローカルな仮定を使用して問題を解決する場合に非常に役立ちます。fたとえば、リストから項目を選択し、実行可能な解決策があるかどうかを確認し、そうでない場合は別の解決策を選択する必要があります。何かのようなもの:

def f(n):
    for x in xrange(n):
        result = slow_function(x):
        if result:
            return result
        [...]

次のようなもっと慣用的なものの方がいいのではないでしょうか。

def f(n):
    for x in xrange(n):
        return slow_function(x) if is not None

これを拡張して、あらゆる種類の値をチェックできます。読みやすいreturn ifステートメントになります。


コード愛好家のための追加の例

数字のリストのリストがあると想像してください:

lists = [[1,2,3],[4,5],[6,7,8],[9,10],...]

また、各リストに 1 つの項目を選択して、選択範囲に最大 1 つの偶数が含まれるようにします。多くのリストが存在する可能性があるため、[1,2,4,...] の選択を開始すると実行可能なソリューションがない可能性があることが既にわかっているため、各組み合わせを試すのは無駄です。

def check(selected):
    even_numbers = filter(lambda n: (n % 2) == 0, selected)
    return len(even_numbers) < 2

def f(lists, selected=[]):
    if not lists:
        return selected

    for n in lists[0]:
        if check(selected + [n]):
            result = f(lists[1:], selected + [n])
            if result:
                return result

次のような構文の方がよいのではないでしょうか。

def f(lists, selected=[]):
    return selected if not lists
    for n in lists[0]:
        if check(selected + [n]):
            return f(lists[1:], selected + [n]) if is not None

これまでに行った最善の方法は、関数を実行可能なソリューションのジェネレーターに変換することです。

def f(lists, selected=[]):
    if not lists:
        yield selected
    else:
        for n in lists[0]:
            if check(selected + [n]):
                for solution in f(lists[1:], selected + [n]):
                    yield solution
4

6 に答える 6

14

他に何を返したいかわからない場合は、いくつかのオプションがあります。

  1. 関数の結果を返すことも、返さないこともできますNone

    return slow_function()
    

    これでは、呼び出し元が値をどう処理するかを知っていることに依存し、None実際にはロジックがどこにあるかをシフトするだけです。

  2. None の代わりに返すデフォルト値がある場合は、次のようにすることができます。

    return slow_function() or default
    

    上記の場合、 (「偽」) の場合slow_functionNone後者の値をslow_function返し、それ以外の場合は「真実」の値を返します。や 0 などのslow_function他の「偽の」値を返すことができる場合、それらは無視されることに注意してください。False[]

  3. または、完全に有効なコードを持っている場合もあります。値と比較し、値の場合はそれを返します。あなたが持っているコードは、それが何をするかが明らかであり、コードの「賢さ」よりも重要な場合があります。

コメントによると、値が次の場合にコードを実行し続ける必要がある場合None最も明白な方法は一時的な値として保存することです。しかし、きれいに読めるので、それは悪いことではありません。

  • 値を計算し、結果として保存します
  • 有効な結果がある場合は、それを返します。
  • それ以外の場合は、より良い結果を得るために何かを続けてください。

ベターは通常非常に主観的なものであり、計算の観点からこれを改善する明らかな方法は見当たりません。また、書かれているように、非常に人間が読める形式であり、これは明らかな利点です。他のソリューションは短くて賢いかもしれませんが、人間が読みやすいということは、コードにとって見過ごされがちな利点です。

于 2013-10-16T23:22:12.743 に答える
10

あなたの最新のコメントは、あなたが何をしたいのかをより明確にするかもしれません:

fa list を渡して項目を選択し、項目なしでリストを渡し、項目がなくなるまでそれ自体を呼び出すと想像してください。解決策が実行可能かどうかを確認します。実行可能である場合は解決策を返します。これはコール スタックを最後まで処理する必要があります。そうでない場合は、None を返します。このようにして、すべての問題をトポロジー順に調査しますが、前に選択したアイテムが実行可能なソリューションを作成できないことがわかっている場合は、チェックをスキップすることもできます。

yieldの代わりに使ってみてもいいかもしれませんreturn。つまり、再帰関数は 1 つの解を生成するのではなく、考えられるすべての解を生成します。具体的な例がなければ、あなたが何をしているのか正確にはわかりませんが、前に次のように言ってください:

def solve(args, result_so_far):
    if not slow_check_is_feasible(result_so_far):
        #dead-end
        return None

    if not args:
        #valid and done
        return result_so_far

    for i, item in enumerate(args):
        #pass list without args - slow
        new_args = args[:]
        del new_args[i]
        result = solve(new_args, accumulate_result(result_so_far, item)
        if result is not None:
            #found it, we are done
            return result
        #otherwise keep going

次のようになります。

def solve_all(args, result_so_far):
    if not slow_check_is_feasible(result_so_far):
        #dead-end
        return

    if not args:
        #yield since result was good
        yield result_so_far
        return

    for i, item in enumerate(args):
        #pass list without args - slow
        new_args = args[:]
        del new_args[i]
        for result in solve(new_args, accumulate_result(result_so_far, item):
            yield result

利点は次のとおりです。

  • 最初の回答だけでなく、すべての回答を生成しますが、それでも 1 つの回答のみが必要な場合は、最初の結果を取得できます。
  • 偽のチェックと回答の両方に戻り値を使用する前に。今、あなたは答えがあるときだけ譲歩しています。
于 2013-10-17T00:21:44.680 に答える
7

基本的に、式を評価し、それをローカル変数にバインドせずに 2 回使用する必要があります。これを行う唯一の方法は、無名変数がないため、それを関数に渡すことです。幸いなことに、現在の関数が戻るかどうかの制御フローは、それが呼び出す関数によって制御されません...ただし、例外は呼び出しスタックに伝播します。

これが良いとは言えませんが、例外を悪用して必要なものを取得できます。これは決して実際に使用するべきではなく、好奇心の練習です。結果は次のようになります (デコレータの使用に注意してください)。

def slow_function(x):
    if x % 5 == 0:
        return x * 200

@if_returner
def foobme(l):
    for i in l:
        print "Checking %s..." % (i,)
        return_if(slow_function(i))

print foobme([2, 3, 4, 5, 6])

出力は次のとおりです。

Checking 2...
Checking 3...
Checking 4...
Checking 5...
1000

秘訣は、例外処理に便乗することです。例外処理は関数呼び出し全体に伝播するからです。気に入ったら、実装は次のとおりです。

class ReturnExc(Exception):
    def __init__(self, val):
        self.val = val

def return_if(val):
    if val is not None:
        raise ReturnExc(val)

def if_returner(f):
    def wrapped(*args, **kwargs):
        try:
            return f(*args, **kwargs)
        except ReturnExc, e:
            return e.val
    return wrapped
于 2013-10-16T23:27:47.167 に答える
2

あまりお勧めではありませんが、リスト内包表記を悪用して、次のようなことを行うことができます。

# Note: Doesn't work in python 3.
def func():
    if [value for value in (slow_function(),) if value is not None]:
        return value
    # continue processing...
于 2013-10-17T02:28:08.970 に答える
-3

あなたが書いたものはうまく見えますが、複数の return ステートメントを避けたい場合は、次のようにすることができます:

def f():
    result = slow_function()
    if result is None:
        [...]
        result = [...]
    return result
于 2013-10-16T23:30:54.303 に答える