先日ハマったのでメモ。
結論
Pythonの引数のデフォルト値は一度しか評価されない。
def func(url, l=[]): …
としたい場合には、代わりに
def func(url, l=None): if l is None: l = [] …
とする。
背景
GitHub APIを叩いて全てのbranch_listを取得しようと、 以下のような関数を書いた。
def fetch_all(url, all_list=[]): res = urllib.request.urlopen(url) res_link, res_body = res.getheader('Link'), res.read().decode('utf-8') all_list += json.loads(res_body) if 'rel="next"' in res_link: next_url = res_link.split('; rel="next"')[0].strip('<>') fetch_all(next_url, all_list) return all_list
が、2度目の fetch_all(url)
実行時には all_listが []
でなく、1度めに実行した際の配列が格納されていることに気付いた。
Pythonチュートリアルを読んでみた
重要な警告: デフォルト値は一度しか評価されない。デフォルト値が可変オブジェクト、すなわちリスト、ディクショナリ、およびほとんどのクラスのインスタンスであると、このことが影響する。

- 作者: Guido van Rossum,鴨澤眞夫
- 出版社/メーカー: オライリージャパン
- 発売日: 2010/02/22
- メディア: 単行本(ソフトカバー)
- 購入: 4人 クリック: 136回
- この商品を含むブログ (23件) を見る
def f(a, L=[]): L.append(a) return L print f(1) # [1] print f(2) # [1, 2] print f(3) # [1, 2, 3]
と実行される。 デフォルト値を共有されたくないのであれば、以下のように書く必要がある。
def f(a, L=None): if L is None: L = [] L.append(a) return L print f(1) # [1] print f(2) # [2] print f(3) # [3]
冒頭の例だと以下のように書き換えると問題なく動作する
def fetch_all(url, all_list=None): # init if all_list is None: all_list = [] res = urllib.request.urlopen(url) res_link, res_body = res.getheader('Link'), res.read().decode('utf-8') all_list += json.loads(res_body) if 'rel="next"' in res_link: next_url = res_link.split('; rel="next"')[0].strip('<>') fetch_all(next_url, all_list) return all_list
なんで一度しか評価されないの?
によると、 def func(item, l = something.defaultList())
のように、関数の実行結果をデフォルト引数とした際に都度実行されることを防ぐ為の目的もあるようだ。