特定の条件をチェックするために他の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つのチェック方法はコストがかかる可能性があるため、複数回呼び出すことは望ましくありません。
あなたはループを使用することができます:
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)
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
変更しないでください
他のさまざまな答えが示すようにこれをする他の方法があります。元のコードほど明確なものはありません。
事実上、timgebと同じ答えですが、より良い書式設定には括弧を使用できます。
def check_all_the_things():
return (
one()
or two()
or five()
or three()
or None
)
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
これは避けます:
...線形を保ちながら、読みやすいフロー。
特定の状況に応じて、さらに優れた関数名を思いつくこともできます。これにより、さらに読みやすくなります。
これは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
として返されます。
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それはかなり滑らかに読みます。
この目的のために作られた組み込みの 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
であることを確認する関数です。 a
がTrue
の場合、f()
は評価されません。checks
にはチェック関数(lambdaのf
項目)が含まれていますFalse
は初期値です。それ以外の場合、チェックは行われず、結果は常にTrue
になります。any
およびreduce
は、関数型プログラミングのための基本的なツールです。 map
と同様にこれらをトレーニングすることを強くお勧めします。
同じコード構造が必要な場合は、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
あなたがそれを見れば、私はこれが素晴らしくそして明確に見えると思います。
デモ:
私にとっては、最善の答えは@ 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
を取得できますが、常に同じタイプになります。
理想的には、値ではなくTrue
またはFalse
を返すようにcheck_
関数を書き直すことをお勧めします。あなたの小切手は
if check_size(x):
return x
#etc
あなたのx
は不変ではないと仮定しても、あなたの関数はそれを変更することができます(再割り当てすることはできません) - しかしcheck
と呼ばれる関数は実際にはそれを変更してはいけません。
上記のMartijnsの最初の例を少し変形したもので、ループの内側にifがあるのを避けます。
Status = None
for c in [check_size, check_color, check_tone, check_flavor]:
Status = Status or c();
return Status
この方法は箱からは少し外れていますが、最終的な結果は単純で読みやすく、見栄えが良いと思います。
基本的な考え方は、関数の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
の値を何千回も取得する予定がない限り、パフォーマンスへの影響はわずかです。
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
私は@ 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()
timeit
をnumber=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
ここに飛び込んで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
または、max
を使用します。
def check_all_conditions():
return max(check_size(), check_color(), check_tone(), check_flavor()) or None