朝はバナナとヨーグルト

毎日同じ朝食でも大丈夫なタイプ

【Python】よくわからん「or」の使い方

きっかけはx = x or []

GitHubの他人のコードで見たときに、「ん?」と口に出た。

1年ほどPythonに触れながら、今までに見たことがなかった使い方だったので焦るわし。

今回はorの変わった使い方と、使い時、良し悪しについて考えてみます!

結論

使わない。

動くけど、意味がわかりづらいので使わないほうが良いと「私は」思います。

より良いとされるif foo is None:といった判定法が推奨されているようです。

具体例

サンプルコード

def append_num(num, group):
    group = group or []
    group.append(num)
    return group


if __name__ == "__main__":
    group1 = [1, 2, 3]
    group2 = None
    print("Before: {}, {}".format(group1, group2))
    group1 = append_num(100, group1)
    group2 = append_num(100, group2)
    print("After: {}, {}".format(group1, group2))

実行結果

Before: [1, 2, 3], None
After: [1, 2, 3, 100], [100]

or []の意味としては、「左がNoneのとき、右を実行する」という認識でいいのでしょうか…

group2はもともとNoneだったので、関数内で空のリストに置き換わります。

もしor []がなかった場合以下のような実行結果になります。

Before: [1, 2, 3], None
Traceback (most recent call last):
  File "main.py", line 104, in <module>
    group2 = append_num(100, group2)
  File "main.py", line 76, in append_num
    group.append(num)
AttributeError: 'NoneType' object has no attribute 'append'

なので、Noneによるエラーを回避する手段として利用している方もいるようですね。

ただGoogle Style Guideを確認したところ、以下のような記述がありました。

Always use if foo is None: (or is not None) to check for a None value-e.g., when testing whether a variable or argument that defaults to None was set to some other value. The other value might be a value that’s false in a boolean context!

Noneかどうかの判定にはif foo is Noneを使え、Falseを示す値はいくつかあるぞ!って感じでしょうか(笑)

試してみたところ、このorは当然かも知れませんがNoneだけでなくFalseのときも同様に動きます。

group2の初期値を0に変更

if __name__ == "__main__":
    group1 = [1, 2, 3]
    group2 = 0 # initialize 0
    print("Before: {}, {}".format(group1, group2))
    group1 = append_num(100, group1)
    group2 = append_num(100, group2)
    print("After: {}, {}".format(group1, group2))

実行結果

Before: [1, 2, 3], 0
After: [1, 2, 3, 100], [100]

よってNoneによる誤作動を防ぐことはできますが、None以外のFalseになる値(0, [], {}, ())を入れてしまっても動いてしまうプログラムの完成です。あかんですね。

なのでGoogle Style Guideでは以下の記述が推奨されていました。

# BAD
def append_num(num, group):
    group = group or []
    group.append(num)
    return group

# GOOD
def append_num(num, group):
    if group is None:
        group = []
    group.append(num)
    return group

結論・まとめ

結果としては、Googleさんも非推奨ですし使わないようにしようと思うます。

あまり直感的にわからない方が自分も含めいらっしゃると思うので、Python大好き人間の集まりみたいなところでない限りは控えたほうが良いというのが率直な感想です…

ちなみに関数の仮引数に空リストを渡すのはエラーのもとなので非推奨だそうな。

def append_num(num, group=[]):
    group.append(num)
    return group


if __name__ == "__main__":
    a = append_num(10)
    a.append(20)
    print(a)
    b = append_num(30)
    b.append(40)
    print(b)

実行結果

[10, 20]
[10, 20, 30, 40]

2つのリストに分けて初期化していると思いきや、abは繋がっているようです。Pythonは参照渡しという噂を聞いたことがあります(間違っていたらすいません、コメント頂けると助かります)が、仮引数の[]によって作成されたリストの参照をabの両方に渡しているため起こるエラーだそうですよ。

orの意外な使い方を説明して燃え尽きてしまいました、andについては実際にやってみてください。私は解説をかけるほどうまい解釈を思いつかなかったので、コメントにandについて残して頂ける方がいらっしゃいましたら、末代まで感謝しますので宜しくおねがいします。