LoginSignup
3
0

More than 3 years have passed since last update.

Pythonではand、orを使うよりもall、anyを使うほうがわかりやすいかも?

Posted at

andorで書く場合

Pythonのif文で複数の条件を指定するときは、andorを使うことが多いと思います。

and1.py
if a > 0 and a % 2 == 0 and a != 10 and a != 100:
    pass

ただ、この書き方では条件が増えると1行が長くなりすぎてしまいます。
しかも、条件の区切りが若干わかりづらい気もします。
そこで改行を入れることもできます。

and2.py
if (
    a > 0 and
    a % 2 == 0 and
    a != 10 and
    a != 100
):
    pass

こう書くこともできます。

and3.py
if (
    a > 0
    and a % 2 == 0
    and a != 10
    and a != 100
):
    pass

1行が短くなって見やすくなりました。
しかし、andがある行とない行があるのが少し気になります。

and4.py
if (
    a > 0 and
    a % 2 == 0 and
    a != 10 and
    a != 100  # この行だけandがないのが気になる
):
    pass

そこでandorの代わりにallanyが使えるんじゃないかと思いました。

allanyで書く場合

allはPythonの組み込み関数で、リストなどの中身がすべてTrueであればTrueを返す関数です。
また、anyは中身に一つでもTrueがあればTrueを返します。
これを使うと上のコードは下のように書き換えることができます。

all1.py
if all([
    a > 0,
    a % 2 == 0,
    a != 10,
    a != 100,
]):
    pass

おお、見やすい!
すべての条件式が揃っているので美しい気がします。

innot inを使って書く場合

さらにinnot inを使うことを思いつきました。

in.py
if False not in [
    a > 0,
    a % 2 == 0,
    a != 10,
    a != 100,
]:
    pass

見た目的にはallを使う場合とあまり変わりません。
しかし、「Falseが入っていない」ということがandと同じということはちょっと直感的ではない気がします。

速度の比較

実行速度はどうでしょうか。比較してみました。

$ python -m timeit 'True and False and False and True'      
10000000 loops, best of 5: 22.7 nsec per loop

$ python -m timeit 'all([True, False, False, True])'        
2000000 loops, best of 5: 102 nsec per loop

$ python -m timeit 'False not in [True, False, False, True]'
10000000 loops, best of 5: 39.6 nsec per loop

$ python -m timeit 'False not in {True, False, False, True}'
10000000 loops, best of 5: 34.4 nsec per loop

こうしてみると、やはりandを使うのが一番速いようです。
not inを使う方法は意外と健闘しています。リストの代わりに集合を使うことで若干高速化されるようです。
この中では一番遅いとはいえ、allを使う方法も致命的に遅いわけではないので、実用的だと思います。

というわけで、条件式が増えた場合、andの代わりにallorの代わりにanyを使うのは良さそうだと思いました。

短絡評価に注意

andを使った場合、一番左の条件がFalseなら、右の条件は評価されずにFalseであると評価されます。
一方でallを使った場合、一旦リストを生成するため、すべての条件が評価されます。

単に右側の条件も評価するから遅い、というだけならあまり問題はないのですが、次のようなケースで問題が起こります。

and5.py
if a is not None and a > 0:  # aはNoneかもしれないが、短絡評価によってエラーが出ない
    pass

これを

all2.py
if all([
    a is not None,  # aはNoneかもしれない
    a > 0,  # aがNoneのときも評価され、エラーが発生する
]):
    pass

こう書き換えてしまうと、コメントに書いたようにエラーが起きてしまいます。
これはどうすればいいんでしょうか。

None判定だけ出すのは少し変な気がします。

all3.py
if all([
    a is not None,
    b is not None,
]) and all([
    a > 100,
    b > 1000,
]):
    pass

短絡評価されるall演算子がほしい。

3
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0