はじめに
こういう状況を考える。
>>> s = "hoge! fuga! piyo!" >>> if "hoge" in s and "fuga" in s and "piyo" in s: ... print("fizz!") ... fizz!
文字列の中に部分文字列が含まれているかを判定する、という状況で、ただ判定したい条件が複数ある。
壮絶にまどろっこしい。できればこんな風に書きたい。
>>> if ("hoge", "fuga", "piyo") in s:
ただ、こんな文法はpythonにはない(というかstrの__contains__メソッドあたりがこういう引数を想定していない)。
これもだめ。
>>> if "hoge" or "fuga" or "piyo" in s:
なんとなくいけそうな気もするけど、実は"hoge" or "fuga" or "piyo"が先に評価されて
>>> "hoge" or "fuga" or "piyo" 'hoge'
となる。pythonの論理演算子は、基本的に受け取ったオブジェクトを返すので、こういう挙動になってしまう。
Python の or と and 演算子の罠 - Qiita
最終的な結果は"hoge" in sなのでまったく正しくない。
なので、これに近づける方法を考える。
スポンサーリンク
all()とany()
どちらも組み込み関数である。all()は論理積、any()は論理和に対応する。
実行例はこんな感じ。
>>> all([False,False]) False >>> all([False,True]) False >>> all([True,True]) True >>> any([False,False]) False >>> any([False,True]) True
これを踏まえ、冒頭のコードを以下のように書き換えてみる。
>>> s = "hoge! fuga! piyo!" >>> if all(x in s for x in ("hoge", "fuga", "piyo")): ... print("fizz!") ... fizz!
まだちょっとダルい。mapでも書いてみる。
>>> s = "hoge! fuga! piyo!" >>> ins_f = lambda x:x in s >>> if all(map(ins_f, ("hoge", "fuga", "piyo"))): ... print("fizz!") ... fizz!
これはエレガントな感じがする。一行増えているけど。一行増やしたくなければ黒魔術を使える。
>>> s = "hoge! fuga! piyo!" >>> if all(map(s.__contains__, ("hoge", "fuga", "piyo"))): ... print("fizz!") ... fizz!
anyもまったく同様にやるだけなので、説明は省略。
注意
ドキュメントを読むとわかるのだが、all(), any()は機能的にはとても単純なようだ。
それは別に良いのだが、先に引数をすべて渡す必要があるせいで、リストを引数に渡した場合、短絡評価されない(というか短絡評価する前に計算してしまっているので無意味)という問題がある。
all(), any()を使うときは引数をジェネレータ式などにしてあげた方が良いだろう。map objectもイテレータで、同様の性質があるので、問題ない。
まとめ
この記事の方法を使うと、複数のin演算子が入るような場合にまとめて簡潔に書くことができる。
あまり使う機会は多くないかもしれないが、参考にしてほしい。