言いたいこと
Python で引数が空かもしれない場合には、次のように or
を使って初期化するのは避けてください。
def f(param = None):
var = param or default
:
かわりに、次のように引数のデフォルト値を直接指定してください。
def f(param = default):
:
前者の書き方だと引数 param
が空の場合にデフォルト値が使われてしまい、意図しない動作になってしまいます。
細かい説明
Python の or
演算子は True
/False
を返すとは限りません。左側の真理値判定の結果が真であれば左側の値を、偽であれば右側の値を返します。
以下に、左側が偽である式の結果を示します。
>>> False or True
True
>>> 0 or 1
1
>>> [] or [1]
[1]
>>> set() or {1}
{1}
>>> {} or {1: 1.0}
{1: 1.0}
>>> None or object()
<object object at 0x0000010DEFB55C30>
これを利用して、引数が省略された場合の処理を簡潔に書くことができますが、この方法は避けていただきたいのです。前述の通り、引数が空であってもデフォルト値が使われてしまうからです。
以下の関数を例に説明します。
from typing import Mapping, Iterable
def mapdict(subject: Iterable[str], mapping: Mapping[str, str] | None = None) -> str:
m = mapping or {"ヰ": "イ", "ヱ": "エ"}
return "".join(m.get(s, s) for s in subject)
この関数は、第一引数の文字列の各文字に、第二引数の辞書を適用し、連結した結果を返します。mapping
引数が省略された場合、デフォルトの辞書 {"ヰ": "イ", "ヱ": "エ"}
が使われます。
以下のようにうまく動くように見えます。
>>> mapdict("ウヰスキー")
'ウイスキー'
>>> mapdict("C:\\path\\to", {"\\": "/"})
'C:/path/to'
しかし、何も変換したくない場合に空の辞書を与えても、デフォルトの辞書が使われてしまいます。空の辞書の真理値は偽だからです。
>>> mapdict("ウヰスキー", {})
'ウイスキー'
この問題を回避するため、できるだけ引数のデフォルト値を直接指定するようにしてください。
from typing import Mapping, Iterable
def mapdict(subject: Iterable[str], mapping: Mapping[str, str] = {"ヰ": "イ", "ヱ": "エ"}) -> str:
return "".join(mapping.get(s, s) for s in subject)
このようにすることで、空の辞書が与えられた場合も正しく動作するようになります。
空の辞書で初期化する場合にも避けてほしい
以下の例は空の辞書で初期化しているので or
を使っても問題ないでしょうか?
from typing import Mapping, Iterable
def mapdict2(subject: Iterable[str], mapping: Mapping[str, str] | None = None) -> str:
m = mapping or {}
ret = ""
for t in subject:
try:
ret += m[t]
except KeyError:
ret += t
return ret
この場合にも、以下のような使われ方をする可能性があるので避けていただきたいです。
>>> from collections import defaultdict
>>> mapdict2("すべてがFになる", defaultdict(lambda: "F"))
'すべてがFになる'
defaultdict(lambda: "F")
はどんなキーが与えられても "F"
を返す辞書です。これを利用してすべての文字を F にしようとしましたが、元の文字列が返ってしまいました。
defaultdict(lambda: "F")
は長さが 0 なので、m = mapping or {}
の mapping
の真理値は偽です。このため、m
は空の辞書 {}
で初期化され、KeyError が発生しています。
この場合も、以下のように引数のデフォルト値を直接指定するようにしてください。
def mapdict2(subject: Iterable[str], mapping: Mapping[str, str] = {}) -> str:
ret = ""
for t in subject:
try:
ret += mapping[t]
except KeyError:
ret += t
return ret
以下の通り、正常に動作するようになります。
>>> mapdict2("すべてがFになる", defaultdict(lambda: "F"))
'FFFFFFFF'