Fumiのブログ

【Python】関数の引数にデフォルト値を設定した時の考察

# はじめに

Python入門者の後輩にグローバル変数とローカル変数について教える参考資料として、「Pythonの変数スコープの話」という記事を使わせて頂いたのですが、その中に「クラスで定義する変数」という項目があり、その中で紹介してあるコードについて検証したので備忘録として書いています。

# 問題のコード

例えば、こんなクラス↓があったとします。

このクラスを使って次のようなコードを作ったとします。

このコードを実行すると次のようになります。

$ python 20180228_sample2.py 
------(1)------
[t1]:  ['test']
[t2]:  None
------(2)------
[t1]:  ['test', 'test']
[t2]:  ['test', 'test']

これを見てわかる通り、(1)は期待通りなのですが(2)は期待通りの結果ではありません。

# 実験

この挙動について調べるために、id関数を使用してメモリアドレスを調べて見ました。 そのためのコードが次の通りです。

このコードを実行すると次のようになります。

$ python 20180228_sample3.py
None    :0x01022877A8
t1      :0x0102B0F780
t2      :0x0102B0F7B8

t1.set()
val     :0x0102B0DC08
common  :0x01022877A8    # Noneのアドレス先
val     :0x0102B0DC08
common  :0x0102B0DC08
------(1)------
[t1]:  ['test']
[t2]:  None

t2.set()
val     :0x0102B0DC08
common  :0x01022877A8    # Noneのアドレス先
val     :0x0102B0DC08
common  :0x0102B0DC08
------(2)------
[t1]:  ['test', 'test']
[t2]:  ['test', 'test']

これからt1とt2は違うインスタンスが生成されているのですが、 Hoge.set関数の引数で渡しているval変数は同じアドレスであることが分かります。

# ドキュメントに振り返ろう

まぁよく言うことなんですが、「まずは公式ドキュメントに振り返ろう」。 ということで、公式ドキュメントでデフォルトの引数値について調べるとちゃんと次のような一文があります。

[原文]

Important warning: The default value is evaluated only once. This makes a difference when the default is a mutable object such as a list, dictionary, or instances of most classes. 『4. More Control Flow Tools — Python 3.6.4 documentation』より抜粋

[日本語翻訳版]

重要な警告: デフォルト値は 1 度だけしか評価されません。デフォルト値がリストや辞書のような変更可能なオブジェクトの時にはその影響がでます。『4. その他の制御フローツール — Python 3.6.4 ドキュメント』より抜粋

ということで、ちゃんと載っています。

# もう一歩検証

まぁ、これまでからデフォルト値は1度だけしか評価されないことが分かりました。 では、変数valをdelしたり、clear関数を呼んだり、[]で初期化したり、self.commonにvalをコピーしたらどうなるのか。 それを試してました。

その結果がこちら

$ python 20180228_sample4.py


====<<< Hoge1 >>>====
None    :0x010FF9D7A8
t1      :0x0110841400
t2      :0x0110841470

t1.set()
val     :0x0110823C08
common  :0x010FF9D7A8
val     :0x0110823C08
common  :0x0110823C08
common  :0x0110823C08
------(1)------
[t1]:  ['test']
[t2]:  None

t2.set()
val     :0x0110823C08
common  :0x010FF9D7A8
val     :0x0110823C08
common  :0x0110823C08
common  :0x0110823C08
------(2)------
[t1]:  ['test', 'test']
[t2]:  ['test', 'test']


====<<< Hoge2 >>>====
None    :0x010FF9D7A8
t1  :0x0110841470
t2  :0x01108414E0

t1.set()
val     :0x011086EA48
common  :0x010FF9D7A8
val     :0x011086EA48
common  :0x011086EA48
val     :0x011086EA48
common  :0x011086EA48
------(1)------
[t1]:  []
[t2]:  None

t2.set()
val     :0x011086EA48
common  :0x010FF9D7A8
val     :0x011086EA48
common  :0x011086EA48
val     :0x011086EA48
common  :0x011086EA48
------(2)------
[t1]:  []
[t2]:  []


====<<< Hoge3 >>>====
None    :0x010FF9D7A8
t1      :0x01108414E0
t2      :0x0110841438

t1.set()
val     :0x011086EA08
common  :0x010FF9D7A8
val     :0x011086EA08
common  :0x011086EA08
val     :0x011086E948
common  :0x011086EA08
------(1)------
[t1]:  ['test']
[t2]:  None

t2.set()
val     :0x011086EA08
common  :0x010FF9D7A8
val     :0x011086EA08
common  :0x011086EA08
val     :0x011086E948
common  :0x011086EA08
------(2)------
[t1]:  ['test', 'test']
[t2]:  ['test', 'test']


====<<< Hoge4 >>>====
None    :0x010FF9D7A8
t1      :0x0110841438
t2      :0x0110841470

t1.set()
val     :0x011086E9C8
common  :0x010FF9D7A8
val     :0x011086E9C8
common  :0x011086E948
------(1)------
[t1]:  ['test']
[t2]:  None

t2.set()
val     :0x011086E9C8
common  :0x010FF9D7A8
val     :0x011086E9C8
common  :0x0110823F88
------(2)------
[t1]:  ['test']
[t2]:  ['test', 'test']


====<<< Hoge5 >>>====
None    :0x010FF9D7A8
t1      :0x0110841470
t2      :0x01108414E0

t1.set()
val     :0x011086E988
common  :0x010FF9D7A8
val     :0x011086E988
common  :0x0110823F88
val     :0x011086E948
common  :0x0110823F88
------(1)------
[t1]:  ['test']
[t2]:  None

t2.set()
val     :0x011086E988
common  :0x010FF9D7A8
val     :0x011086E988
common  :0x011086E948
val     :0x011086EAC8
common  :0x011086E948
------(2)------
[t1]:  ['test']
[t2]:  ['test', 'test']

ということで、思い通りの結果になったのはvalをself.commonにコピーした時でした。

# 結論

まぁ、デフォルト値を使うならいきなりaddpend関数で要素を足すというのが、まずどうなんだろう...って感じがしますので、 まずはあのようなコードは書かないようにするのが大切かもしれないですね。

参考