web-dev-qa-db-ja.com

「if x:return x」ステートメントを回避するためのPythonicの方法

特定の条件をチェックするために他の4つのメソッドを順番に呼び出し、Truthyが返されるたびに(次のチェックを行わずに)すぐに戻るメソッドがあります。

def check_all_conditions():
    x = check_size()
    if x:
        return x

    x = check_color()
    if x:
        return x

    x = check_tone()
    if x:
        return x

    x = check_flavor()
    if x:
        return x
    return None

これは多くの手荷物コードのようです。 2行ごとのif文の代わりに、次のようにします。

x and return x

しかしそれは無効なPythonです。シンプルでエレガントな解決策が欠けていますか?ちなみに、このような状況では、これら4つのチェック方法はコストがかかる可能性があるため、複数回呼び出すことは望ましくありません。

216
Bernard

あなたはループを使用することができます:

conditions = (check_size, check_color, check_tone, check_flavor)
for condition in conditions:
    result = condition()
    if result:
        return result

これには、条件数を可変にできるという追加の利点があります。

あなたは map() + filter() (Python 2ではPython 3バージョン、 future_builtinsバージョン を使う)を使うことができます:

try:
    # Python 2
    from future_builtins import map, filter
except ImportError:
    # Python 3
    pass

conditions = (check_size, check_color, check_tone, check_flavor)
return next(filter(None, map(lambda f: f(), conditions)), None)

しかし、これがもっと読みやすい場合は議論の余地があります。

もう1つの選択肢は、ジェネレータ式を使用することです。

conditions = (check_size, check_color, check_tone, check_flavor)
checks = (condition() for condition in conditions)
return next((check for check in checks if check), None)
275
Martijn Pieters

Martijnの良い答えに代えて、あなたはorをチェーンすることができます。これは最初の真の値を返します。真の値がない場合はNoneを返します。

def check_all_conditions():
    return check_size() or check_color() or check_tone() or check_flavor() or None

デモ:

>>> x = [] or 0 or {} or -1 or None
>>> x
-1
>>> x = [] or 0 or {} or '' or None
>>> x is None
True
391
timgeb

変更しないでください

他のさまざまな答えが示すようにこれをする他の方法があります。元のコードほど明確なものはありません。

84
Jack Aidley

事実上、timgebと同じ答えですが、より良い書式設定には括弧を使用できます。

def check_all_the_things():
    return (
        one()
        or two()
        or five()
        or three()
        or None
    )
82
Wayne Werner

Curlyの法則 によれば、このコードを読みやすくするためには、次の2つのことが考えられます。

  • 何をチェックしますか?
  • 一つのことが真実を返しましたか?

二つの機能に分けられます。

def all_conditions():
    yield check_size()
    yield check_color()
    yield check_tone()
    yield check_flavor()

def check_all_conditions():
    for condition in all_conditions():
        if condition:
            return condition
    return None

これは避けます:

  • 複雑な論理構造
  • 本当に長い行
  • 繰り返し

...線形を保ちながら、読みやすいフロー。

特定の状況に応じて、さらに優れた関数名を思いつくこともできます。これにより、さらに読みやすくなります。

73
Phil Frost

これはMartijnsの最初の例の変形です。また、短絡を可能にするために "callableのコレクション"スタイルを使用します。

ループの代わりに組み込みのanyを使うことができます。

conditions = (check_size, check_color, check_tone, check_flavor)
return any(condition() for condition in conditions) 

anyはブール値を返すので、チェックの正確な戻り値が必要な場合、この解決策は機能しません。 anyは戻り値として14'red''sharp''spicy'を区別しません。それらはすべてTrueとして返されます。

41
Leonhard

if x: return xを1行にすべて書くことを考えたことはありますか?

def check_all_conditions():
    x = check_size()
    if x: return x

    x = check_color()
    if x: return x

    x = check_tone()
    if x: return x

    x = check_flavor()
    if x: return x

    return None

これはあなたが持っていたものよりも少なくありません繰り返しのしかし、IMNSHOそれはかなり滑らかに読みます。

26
zwol

この目的のために作られた組み込みの any については、誰も言及していません。

def check_all_conditions():
    return any([
        check_size(),
        check_color(),
        check_tone(),
        check_flavor()
    ])

この実装はおそらく最も明確なものですが、最初のチェックがTrueであってもすべてのチェックを評価します。


本当に最初の失敗したチェックで止める必要があるなら、リストを単純な値に変換するために作られた reduce を使うことを考えてください:

def check_all_conditions():
    checks = [check_size, check_color, check_tone, check_flavor]
    return reduce(lambda a, f: a or f(), checks, False)

reduce(function, iterable[, initializer]):イテラブルを単一の値にするために、二つの引数の関数をイテラブルの項目に左から右へ累積的に適用します。左側の引数xは累積値、右側の引数yはイテラブルからの更新値です。オプションの初期化子が存在する場合、それは計算においてイテラブルの項目の前に置かれます。

あなたの場合:

  • lambda a, f: a or f()は、アキュムレータaまたは現在のチェックf()Trueであることを確認する関数です。 aTrueの場合、f()は評価されません。
  • checksにはチェック関数(lambdaのf項目)が含まれています
  • Falseは初期値です。それ以外の場合、チェックは行われず、結果は常にTrueになります。

anyおよびreduceは、関数型プログラミングのための基本的なツールです。 map と同様にこれらをトレーニングすることを強くお勧めします。

23
blint

同じコード構造が必要な場合は、3項ステートメントを使用できます。

def check_all_conditions():
    x = check_size()
    x = x if x else check_color()
    x = x if x else check_tone()
    x = x if x else check_flavor()

    return x if x else None

あなたがそれを見れば、私はこれが素晴らしくそして明確に見えると思います。

デモ:

Screenshot of it running

18
Phinet

私にとっては、最善の答えは@ phil-frostからで、その後に@ wayne-werner'sが続きます。

私がおもしろいと思うのは、関数が多くの異なるデータ型を返すという事実について誰も何も言っていないということです。それはそれ以上の仕事をするためにx自体の型をチェックすることを義務付けます。

それで、私は@ PhilFrostの応答を単一の型を保持するという考えと混ぜ合わせるでしょう:

def all_conditions(x):
    yield check_size(x)
    yield check_color(x)
    yield check_tone(x)
    yield check_flavor(x)

def assessed_x(x,func=all_conditions):
    for condition in func(x):
        if condition:
            return x
    return None

xが引数として渡されるだけでなく、all_conditionsも検査関数の渡されたジェネレータとして使用され、そのすべてが検査対象のxを取得し、TrueまたはFalseを返すことに注意してください。デフォルト値としてall_conditionsと共にfuncを使用することで、assessed_x(x)を使用することができます。あるいは、funcを介してさらにパーソナライズされたジェネレータを渡すことができます。

そうすれば、1つのチェックがパスするとすぐにxを取得できますが、常に同じタイプになります。

4
juandesant

理想的には、値ではなくTrueまたはFalseを返すようにcheck_関数を書き直すことをお勧めします。あなたの小切手は

if check_size(x):
    return x
#etc

あなたのxは不変ではないと仮定しても、あなたの関数はそれを変更することができます(再割り当てすることはできません) - しかしcheckと呼ばれる関数は実際にはそれを変更してはいけません。

3
RoadieRich

上記のMartijnsの最初の例を少し変形したもので、ループの内側にifがあるのを避けます。

Status = None
for c in [check_size, check_color, check_tone, check_flavor]:
  Status = Status or c();
return Status
3
mathreadler

この方法は箱からは少し外れていますが、最終的な結果は単純で読みやすく、見栄えが良いと思います。

基本的な考え方は、関数の1つが真と評価されたときに例外をraiseにして、結果を返すことです。外観は次のとおりです。

def check_conditions():
    try:
        assertFalsey(
            check_size,
            check_color,
            check_tone,
            check_flavor)
    except TruthyException as e:
        return e.trigger
    else:
        return None

呼び出された関数の引数の1つがtrueと評価された場合に例外を発生させるassertFalsey関数が必要です。

def assertFalsey(*funcs):
    for f in funcs:
        o = f()
        if o:
            raise TruthyException(o)

評価される関数にも引数を提供するように上記を修正することができます。

そしてもちろんTruthyException自体も必要です。この例外は、例外を引き起こしたobjectを提供します。

class TruthyException(Exception):
    def __init__(self, obj, *args):
        super().__init__(*args)
        self.trigger = obj

もちろん、元の関数をもっと一般的なものに変えることができます。

def get_truthy_condition(*conditions):
    try:
        assertFalsey(*conditions)
    except TruthyException as e:
        return e.trigger
    else:
        return None

result = get_truthy_condition(check_size, check_color, check_tone, check_flavor)

ifステートメントと例外処理の両方を使用しているので、これは少し遅くなるかもしれません。ただし、例外は最大1回しか処理されないため、チェックを実行してTrueの値を何千回も取得する予定がない限り、パフォーマンスへの影響はわずかです。

2
Rick Teachey

Pythonicの方法はreduce(すでに述べたように)かitertools(以下に示すように)のどちらかを使うことですが、or演算子の単純な短絡を使うともっと明確なコードを生成するようです

from itertools import imap, dropwhile

def check_all_conditions():
    conditions = (check_size,\
        check_color,\
        check_tone,\
        check_flavor)
    results_gen = dropwhile(lambda x:not x, imap(lambda check:check(), conditions))
    try:
        return results_gen.next()
    except StopIteration:
        return None
1

私は@ timgebが好きです。それまでの間、Noneで区切られたステートメントのコレクションが評価され、最初のnone-zero、none-empty、none-Noneが返されるので、returnステートメントでorを表現する必要はありません。 Noneがあるかどうかにかかわらず、Noneが返されます。

だから私のcheck_all_conditions()関数はこのようになります:

def check_all_conditions():
    return check_size() or check_color() or check_tone() or check_flavor()

timeitnumber=10**7と一緒に使う私はいくつかの提案の実行時間を調べました。比較のためにrandom.random()関数を使って文字列または乱数に基づくNoneを返しました。これが全体のコードです。

import random
import timeit

def check_size():
    if random.random() < 0.25: return "BIG"

def check_color():
    if random.random() < 0.25: return "RED"

def check_tone():
    if random.random() < 0.25: return "SOFT"

def check_flavor():
    if random.random() < 0.25: return "SWEET"

def check_all_conditions_Bernard():
    x = check_size()
    if x:
        return x

    x = check_color()
    if x:
        return x

    x = check_tone()
    if x:
        return x

    x = check_flavor()
    if x:
        return x
    return None

def check_all_Martijn_Pieters():
    conditions = (check_size, check_color, check_tone, check_flavor)
    for condition in conditions:
        result = condition()
        if result:
            return result

def check_all_conditions_timgeb():
    return check_size() or check_color() or check_tone() or check_flavor() or None

def check_all_conditions_Reza():
    return check_size() or check_color() or check_tone() or check_flavor()

def check_all_conditions_Phinet():
    x = check_size()
    x = x if x else check_color()
    x = x if x else check_tone()
    x = x if x else check_flavor()

    return x if x else None

def all_conditions():
    yield check_size()
    yield check_color()
    yield check_tone()
    yield check_flavor()

def check_all_conditions_Phil_Frost():
    for condition in all_conditions():
        if condition:
            return condition

def main():
    num = 10000000
    random.seed(20)
    print("Bernard:", timeit.timeit('check_all_conditions_Bernard()', 'from __main__ import check_all_conditions_Bernard', number=num))
    random.seed(20)
    print("Martijn Pieters:", timeit.timeit('check_all_Martijn_Pieters()', 'from __main__ import check_all_Martijn_Pieters', number=num))
    random.seed(20)
    print("timgeb:", timeit.timeit('check_all_conditions_timgeb()', 'from __main__ import check_all_conditions_timgeb', number=num))
    random.seed(20)
    print("Reza:", timeit.timeit('check_all_conditions_Reza()', 'from __main__ import check_all_conditions_Reza', number=num))
    random.seed(20)
    print("Phinet:", timeit.timeit('check_all_conditions_Phinet()', 'from __main__ import check_all_conditions_Phinet', number=num))
    random.seed(20)
    print("Phil Frost:", timeit.timeit('check_all_conditions_Phil_Frost()', 'from __main__ import check_all_conditions_Phil_Frost', number=num))

if __== '__main__':
    main()

そして、これが結果です。

Bernard: 7.398444877040768
Martijn Pieters: 8.506569201346597
timgeb: 7.244275416364456
Reza: 6.982133448743038
Phinet: 7.925932800076634
Phil Frost: 11.924794811353031
1
Reza Dodge

ここに飛び込んでPythonの単一行を書いたことは一度もありませんが、if x = check_something(): return xが有効であると思いますか?

もしそうなら:

def check_all_conditions():

    if (x := check_size()): return x
    if (x := check_color()): return x
    if (x := check_tone()): return x
    if (x := check_flavor()): return x

    return None
0
Richard87

または、maxを使用します。

def check_all_conditions():
    return max(check_size(), check_color(), check_tone(), check_flavor()) or None
0
U10-Forward