Python遅延バインディング問題の原理と解決策


遅延バインディングはクローズド問題に現れる。以下は閉包の例を見ます。

def (n):
  def mul(x):
    return n*x
  return mul
double = gen_mul(2)
doubled_value = double(6) 
閉包を満たす点がいくつか見られます。
  • は内部関数
  • があります。
  • 内部関数は、外部関数の自由変数
  • を参照している。
  • 内部関数が返されます。
  • クローズドの利点:
  • は、グローバル変数
  • の使用を避けることができる。
  • は、静的変数の役割を果たす変数を永続化することができます。
  • クローズドの欠点:
  • は大量のメモリを消費する可能性があります。
  • メモリ漏れの原因となる可能性があります。
    もちろん欠点は人為的に避けられます。
    もう一つは遅延バインディングを引き出す例を見てみよう。
    
    def multipliers():
      return [lambda x : i * x for i in range(4)]
    print([m(2) for m in multipliers()]) # [6,6,6,6]
    上記の例は[6,6,6,6,6]を出力します。私達が予想していた[0,2,4,6]ではありません。
    これは結合遅延による結果である。具体的な過程を分析してみます。
    3行目を実行すると、まずmultiliers関数を実行し、関数のリスト解析式を実行します。反復するたびに、要素として匿名関数が生成されます。その後、3行目に戻り、戻ったリストの匿名関数を巡回してパラメータ2に入力して実行します。この時の関数は以下のようです。
    def noname(x):
    return i*x
    Pythonが変数の作用ドメインチェーンを検索する順序は順次LEGBであることを知っています。
    局所変数(L)->外部関数における局所変数(E)->グローバル変数(G)->内蔵変数(B)
    非常に重要な点は、Pythonのスコープがコンパイルされた時に形成されていますが、実行中ではなく、関数のスコープが呼び出された位置とは関係がありません。
    この例では、上のnoname関数体のiはどこから来ますか?もちろん、まずmultiliers関数の局所変数を探しに行きます。このときiの値はすでに3なので、このような「難解」な現象が発生します。
    今は原因が分かりましたが、どう解決すればいいですか?
    反復i値を直接匿名関数の関数に注入することができ、ここでは2つの方法を与える。
    パラメータのデフォルト値を設定すると、コンパイル時にデフォルト値が計算されます。
    def multiliers_ch 1():
    return[lamboda m,x=i:m*x for i in range(4)]
    内蔵関数でpartial:
    
    from functools import partial
    def multipliers_ch2():
      return [partial(lambda m,x : m * x,i) for i in range(4)]
    生成器の遅延計算を利用します。
    
    def multipliers_ch3():
      for m in range(4):
        yield lambda x: m * x
    partialとジェネレータの内容は後で共有します。
    実行結果
    print([m(2)form in multiplers_ch 1())(菗[0,2,4,6]
    print([m(2)form in multiplers_ch 2()))(ハ[0,2,4,6]
    print([m(2)form in multiplers_ch 3())(菗[0,2,4,6]
    注:
    自由変数:ローカルスコープに結合されていない変数を指します。関数のコード属性にアクセスすることで確認できます。
    fun.com.freevars
    LEGB:この部分で説明できます。
    以上が本文の全部です。皆さんの勉強に役に立つように、私たちを応援してください。