Pythonでは、変数に初期値がない場合、その変数にNoneを設定することが一般的です。また、関数の引数のデフォルト値としてNoneを設定し、その後で具体的な値を代入することもよくあります。
例えば、PytorchのResnetの実装では、norm_layerという引数のデフォルト値をNoneに設定し、その後でnn.BatchNorm2dを代入しています。
class BasicBlock(nn.Module):
def __init__(self, ...(その他引数省略) norm_layer: Optional[Callable[..., nn.Module]] = None) -> None:
super(BasicBlock, self).__init__()
if norm_layer is None:
norm_layer = nn.BatchNorm2d
...(実装続く)
このようにデフォルト値をNoneに設定する理由は、デフォルト値は実行時、モジュールがロードされた時ただ1度だけ評価されるため、動的な値に奇妙な振る舞いをもたらす可能性があるからです。
具体的な例を見てみましょう。以下の関数は、データを受け取り、json形式であればそれを辞書型に変換し、jsonでなければ空の辞書を返す関数です。
def bad_decode(data, default={}):
try:
return json.loads(data)
except ValueError:
return default
この関数を使って以下のように実行すると、予想外の結果が得られます。
foo = bad_decode('bad data')
foo['staff'] = 5
bar = bad_decode('also bad data')
bar['meep'] = 1
print("foo", foo)
print("bar", bar)
# 出力: foo {'staff': 5, 'meep': 1}
# 出力: bar {'staff': 5, 'meep': 1}
これを防ぐためには、デフォルト値をNoneに設定し、関数内で具体的な値を代入するようにします。
def good_decode(data, default=None):
if default is None:
default = {}
try:
return json.loads(data)
except ValueError:
return default
このようにすると、期待通りの結果が得られます。
foo = good_decode('bad data')
foo['staff'] = 5
bar = good_decode('also bad data')
bar['meep'] = 1
print("foo", foo)
print("bar", bar)
# 出力: foo {'staff': 5}
# 出力: bar {'meep': 1}
以上のように、Pythonではデフォルト値をNoneに設定することで、予期せぬ挙動を防ぐことができます。この知識を持つことで、Pythonのコードをより理解しやすく、安全に書くことができます。