Python関数のデフォルトパラメータについて


Pythonの奇妙な点の一つは、def文が実行されたときに一度だけ計算される関数のデフォルトパラメータの値です.これはどんな問題を引き起こすのでしょうか.
奇抜な例
例を見てみましょう
In [44]: def packitem(item, pkg = []):
   ....:         pkg.append(item)
   ....:         return pkg
 
In [45]: l = [100,200]
 
In [46]: packitem(300, l)
Out[46]: [100, 200, 300]
 
In [47]: packitem(1)
Out[47]: [1]
 
In [48]: packitem(2)
Out[48]: [1, 2]
 
In [49]: packitem(3)
Out[49]: [1, 2, 3]

これはpackitemのデフォルトパラメータpkg=[]が1回しか計算されていないことを示している.その後のpackitem関数が呼び出されると、pkgは最初に作成されたリストを指します.
どうして
どうしてこんなことになったの?
Pythonからコンパイルされたバイトコードから答えを求める必要があります.
In [65]: def main():
   ....:         def packitem(item, pkg = []):
   ....:                 pkg.append(item)
   ....:                 return pkg
   ....:         print packitem(1)
   ....:         print packitem(2)
   ....:         print packitem(3)    
 
In [66]: main()
[1]
[1, 2]
[1, 2, 3]
 
In [67]: dis.dis(main)
  2           0 BUILD_LIST               0
              3 LOAD_CONST               1 (code object)
              6 MAKE_FUNCTION            1
              9 STORE_FAST               0 (packitem)
 
  5          12 LOAD_FAST                0 (packitem)
             15 LOAD_CONST               2 (1)
             18 CALL_FUNCTION            1
             21 PRINT_ITEM
             22 PRINT_NEWLINE       
 
  6          23 LOAD_FAST                0 (packitem)
             26 LOAD_CONST               3 (2)
             29 CALL_FUNCTION            1
             32 PRINT_ITEM
             33 PRINT_NEWLINE       
 
  7          34 LOAD_FAST                0 (packitem)
             37 LOAD_CONST               4 (3)
             40 CALL_FUNCTION            1
             43 PRINT_ITEM
             44 PRINT_NEWLINE
             45 LOAD_CONST               0 (None)
             48 RETURN_VALUE

わかる.packitem関数のデフォルトパラメータpkgの値は、最初のバイトコードで作成されます.その後MAKE_FUNCTION命令の場合、コードオブジェクトと一緒に関数オブジェクトにパッケージ化し、STORE_FAST 0はFASTテーブルの0位に存在する.
後続の関数呼び出しLOAD_FAST 0コマンドpackitemの関数オブジェクトを取り出し、CALL_FUNCTIONコール(CALL_FUNCTIONについては、後述の記事で検討します).関数呼び出しのプロセス全体がデフォルトのパラメータ値の初期化に関与していません.
したがって、Python関数のデフォルトパラメータの値は、関数定義時にのみ計算され、後続の関数呼び出し時のデフォルトパラメータは、最初に作成されたオブジェクトを参照していることがわかります.
Hack It
Pythonが関数呼び出しを行うときに再作成してくれたデフォルトパラメータの値がない以上、私たちは自分で手を出して、衣食を豊かにします.
1つ目のスキームは,noneのような可変のデフォルト値を用いて関数内部で判断することである.この方法は少し面倒だ.
第2の方法は装飾器によってこの問題を解決することである.
このシナリオはSean Rossが書いたもので、彼に感謝しています.
In [74]: def freshdefaults(f):
   ....:         fdefaults = f.func_defaults
   ....:         def refresher(*args, **kwds):
   ....:                 f.func_defaults = copy.deepcopy(fdefaults)
   ....:                 return f(*args, **kwds)
   ....:         return refresher
 
In [75]: @freshdefaults
   ....: def packitem(item, pkg = []):
   ....:         pkg.append(item)
   ....:         return pkg
 
In [76]: l = [100,200]
 
In [77]: packitem(300, l)
Out[77]: [100, 200, 300]
 
In [78]: packitem(1)
Out[78]: [1]
 
In [79]: packitem(2)
Out[79]: [2]
 
In [80]: packitem(3)
Out[80]: [3]

packitemの出力は私たちの予想に合っていることがわかります.デコレーションfreshdefaultにより、デフォルトパラメータの更新を完了しました.packitemのpkgは呼び出しのたびに更新されています.
装飾器は
myfunc = wrapper(myfunc)

この例では,後に一言を加えるのと等価である.
packitem = freshdefault(packitem)

リファレンス
  • Python Cookbook
  • Pythonソース剖析
  • (全文完了)
    このリンクは次のとおりです.http://everet.org/2012/06/python-function-default-parameter.html(転載時に著者と出典を明記してください.)