静かなる名辞

pythonとプログラミングのこと


【python】複数のin演算子を一つにまとめる方法

はじめに

 こういう状況を考える。

>>> 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()は機能的にはとても単純なようだ。

組み込み関数 — Python 3.7.4 ドキュメント

 それは別に良いのだが、先に引数をすべて渡す必要があるせいで、リストを引数に渡した場合、短絡評価されない(というか短絡評価する前に計算してしまっているので無意味)という問題がある。

 all(), any()を使うときは引数をジェネレータ式などにしてあげた方が良いだろう。map objectもイテレータで、同様の性質があるので、問題ない。

www.haya-programming.com

まとめ

 この記事の方法を使うと、複数のin演算子が入るような場合にまとめて簡潔に書くことができる。

 あまり使う機会は多くないかもしれないが、参考にしてほしい。