273

Pythonでネストされた関数を見て使用しましたが、それらはクロージャの定義と一致します。では、なぜ彼らはnested functions代わりに呼ばれるのclosuresですか?

入れ子関数は外界で使用されていないため、クロージャではありませんか?

更新:私はクロージャについて読んでいて、Pythonに関してこの概念について考えるようになりました。下のコメントで誰かが言及している記事を検索して見つけましたが、その記事の説明が完全に理解できなかったので、この質問をしています。

4

8 に答える 8

424

クロージャは、関数が実行を終了した囲んでいるスコープからローカル変数にアクセスできるときに発生します。

def make_printer(msg):
    def printer():
        print(msg)
    return printer

printer = make_printer('Foo!')
printer()

make_printer呼び出されると、関数のコンパイル済みコードをprinter定数として、値をmsgローカルとして、新しいフレームがスタックに配置されます。次に、関数を作成して返します。printer関数は変数を参照するため、関数が戻ったmsg後も変数は存続します。make_printer

したがって、ネストされた関数がそうでない場合

  1. 囲んでいるスコープに対してローカルなアクセス変数、
  2. それらがその範囲外で実行されるときにそうします、

その後、それらはクロージャではありません。

これは、クロージャーではない入れ子関数の例です。

def make_printer(msg):
    def printer(msg=msg):
        print(msg)
    return printer

printer = make_printer("Foo!")
printer()  #Output: Foo!

ここでは、値をパラメーターのデフォルト値にバインドしています。これは関数printerが作成されたときに発生するため、returnの後にmsgexternaltoの値への参照をprinter 維持する必要はありませんmake_printer。このコンテキストmsgでは、関数の通常のローカル変数です。printer

于 2010-10-26T03:20:12.940 に答える
111

質問はすでに aaronasterlingによって回答されています

ただし、誰かが変数が内部でどのように格納されるかに興味があるかもしれません。

スニペットに来る前に:

クロージャは、囲んでいる環境から変数を継承する関数です。I / Oを実行する別の関数に引数として関数コールバックを渡すと、このコールバック関数は後で呼び出され、この関数は、ほぼ魔法のように、宣言されたコンテキストと、使用可能なすべての変数を記憶します。その文脈で。

  • 関数が自由変数を使用しない場合、クロージャは形成されません。

  • 自由変数を使用する別の内部レベルがある場合-以前のすべてのレベルは字句環境を保存します(最後の例)

  • python<3.Xまたはpython>3.Xの関数属性func_closure、自由変数を保存します。__closure__

  • Pythonのすべての関数にはクロージャー属性がありますが、自由変数がない場合は空です。

例:クロージャー属性ですが、自由変数がないため、内部にコンテンツはありません。

>>> def foo():
...     def fii():
...         pass
...     return fii
...
>>> f = foo()
>>> f.func_closure
>>> 'func_closure' in dir(f)
True
>>>

注意:自由変数はクロージャを作成する必要があります。

上記と同じスニペットを使用して説明します。

>>> def make_printer(msg):
...     def printer():
...         print msg
...     return printer
...
>>> printer = make_printer('Foo!')
>>> printer()  #Output: Foo!

また、すべてのPython関数にはクロージャ属性があるため、クロージャ関数に関連付けられている囲んでいる変数を調べてみましょう。

func_closure関数の属性は次のとおりですprinter

>>> 'func_closure' in dir(printer)
True
>>> printer.func_closure
(<cell at 0x108154c90: str object at 0x108151de0>,)
>>>

このclosure属性は、囲んでいるスコープで定義された変数の詳細を含むセルオブジェクトのタプルを返します。

func_closureの最初の要素は、Noneまたは関数の自由変数のバインディングを含むセルのタプルであり、読み取り専用です。

>>> dir(printer.func_closure[0])
['__class__', '__cmp__', '__delattr__', '__doc__', '__format__', '__getattribute__',
 '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', 
 '__setattr__',  '__sizeof__', '__str__', '__subclasshook__', 'cell_contents']
>>>

上記の出力で見ることができますcell_contents、それが何を保存するか見てみましょう:

>>> printer.func_closure[0].cell_contents
'Foo!'    
>>> type(printer.func_closure[0].cell_contents)
<type 'str'>
>>>

したがって、関数を呼び出すと、関数は。printer()内に格納されている値にアクセスしますcell_contents。これが、「Foo!」として出力を取得した方法です。

繰り返しになりますが、上記のスニペットをいくつか変更して使用して説明します。

 >>> def make_printer(msg):
 ...     def printer():
 ...         pass
 ...     return printer
 ...
 >>> printer = make_printer('Foo!')
 >>> printer.func_closure
 >>>

上記のスニペットでは、printer関数内にmsgを出力しなかったため、自由変数は作成されません。自由変数がないため、クロージャー内にコンテンツはありません。それはまさに私たちが上で見たものです。

次に、すべてをクリアするための別のスニペットについて説明しFree VariableますClosure

>>> def outer(x):
...     def intermediate(y):
...         free = 'free'
...         def inner(z):
...             return '%s %s %s %s' %  (x, y, free, z)
...         return inner
...     return intermediate
...
>>> outer('I')('am')('variable')
'I am free variable'
>>>
>>> inter = outer('I')
>>> inter.func_closure
(<cell at 0x10c989130: str object at 0x10c831b98>,)
>>> inter.func_closure[0].cell_contents
'I'
>>> inn = inter('am')

したがって、func_closureプロパティはクロージャセルのタプルであることがわかり、それらとそのコンテンツを明示的に参照できます。セルにはプロパティ「cell_contents」があります。

>>> inn.func_closure
(<cell at 0x10c9807c0: str object at 0x10c9b0990>, 
 <cell at 0x10c980f68: str object at   0x10c9eaf30>, 
 <cell at 0x10c989130: str object at 0x10c831b98>)
>>> for i in inn.func_closure:
...     print i.cell_contents
...
free
am 
I
>>>

ここでを呼び出すとinn、すべての自由変数を参照するため、次のようになります。I am free variable

>>> inn('variable')
'I am free variable'
>>>
于 2014-01-03T06:41:20.730 に答える
80

Pythonはクロージャのサポートが弱いです。私が何を意味するかを理解するために、JavaScriptでクロージャーを使用するカウンターの次の例を見てください。

function initCounter(){
    var x = 0;
    function counter  () {
        x += 1;
        console.log(x);
    };
    return counter;
}

count = initCounter();

count(); //Prints 1
count(); //Prints 2
count(); //Prints 3

クロージャは、このように記述された関数に「内部メモリ」を持つ機能を提供するため、非常にエレガントです。Python 2.7以降、これは不可能です。やってみたら

def initCounter():
    x = 0;
    def counter ():
        x += 1 ##Error, x not defined
        print x
    return counter

count = initCounter();

count(); ##Error
count();
count();

xが定義されていないというエラーが表示されます。しかし、あなたがそれを印刷できることが他の人によって示されているとしたら、どうしてそれができるでしょうか?これは、Pythonが関数変数スコープを管理する方法が原因です。内部関数は外部関数の変数を読み取ることはできますが、書き込むことはできません。

これは本当に残念です。しかし、読み取り専用クロージャを使用するだけで、少なくともPythonが構文糖衣を提供する関数デコレータパターンを実装できます。

アップデート

指摘されているように、Pythonのスコープ制限に対処する方法がいくつかあり、いくつかを公開します。

1.globalキーワードを使用します(通常はお勧めしません)。

2. Python 3.xでは、nonlocalキーワードを使用します(@unutbuおよび@leewzが提案)

3.単純な変更可能なクラスを定義しますObject

class Object(object):
    pass

変数を格納するためのObject scope内を作成し ますinitCounter

def initCounter ():
    scope = Object()
    scope.x = 0
    def counter():
        scope.x += 1
        print scope.x

    return counter

scopeは実際には単なる参照であるため、そのフィールドで実行されたアクションは実際にはそれ自体を変更しないためscope、エラーは発生しません。

4. @unutbuが指摘したように、別の方法は、各変数を配列()として定義し、そのx = [0]最初の要素(x[0] += 1)を変更することです。xこの場合も、それ自体が変更されていないため、エラーは発生しません。

5. @raxacoricofallapatoriusによって提案されているように、次xのプロパティを作成できます。counter

def initCounter ():

    def counter():
        counter.x += 1
        print counter.x

    counter.x = 0
    return counter
于 2014-05-09T07:22:27.090 に答える
22

Python 2にはクロージャがありませんでした。クロージャにた、回避策がありました。

すでに与えられた答えにはたくさんの例があります-変​​数を内部関数にコピーする、内部関数のオブジェクトを変更するなど。

Python 3では、サポートはより明確で簡潔です。

def closure():
    count = 0
    def inner():
        nonlocal count
        count += 1
        print(count)
    return inner

使用法:

start = closure()
another = closure() # another instance, with a different stack

start() # prints 1
start() # prints 2

another() # print 1

start() # prints 3

nonlocalキーワードは、内部関数を明示的に言及された外部変数にバインドし、事実上それを囲みます。したがって、より明確に「閉鎖」。

于 2017-03-14T01:49:50.913 に答える
9

別の、しかし永続的な名前空間が必要な状況がありました。クラスを使用しました。そうでなければしません。分離されているが永続的な名前はクロージャです。

>>> class f2:
...     def __init__(self):
...         self.a = 0
...     def __call__(self, arg):
...         self.a += arg
...         return(self.a)
...
>>> f=f2()
>>> f(2)
2
>>> f(2)
4
>>> f(4)
8
>>> f(8)
16

# **OR**
>>> f=f2() # **re-initialize**
>>> f(f(f(f(2)))) # **nested**
16

# handy in list comprehensions to accumulate values
>>> [f(i) for f in [f2()] for i in [2,2,4,8]][-1] 
16
于 2014-06-24T17:38:54.470 に答える
6
def nested1(num1): 
    print "nested1 has",num1
    def nested2(num2):
        print "nested2 has",num2,"and it can reach to",num1
        return num1+num2    #num1 referenced for reading here
    return nested2

与える:

In [17]: my_func=nested1(8)
nested1 has 8

In [21]: my_func(5)
nested2 has 5 and it can reach to 8
Out[21]: 13

これは、クロージャとは何か、およびその使用方法の例です。

于 2014-01-28T11:57:34.423 に答える
3

人々は閉鎖が何であるかについて混乱しています。クロージャは内部機能ではありません。閉鎖の意味は閉鎖の行為です。したがって、内部関数は、自由変数と呼ばれる非ローカル変数を閉じています。

def counter_in(initial_value=0):
    # initial_value is the free variable
    def inc(increment=1):
        nonlocal initial_value
        initial_value += increment
        return print(initial_value)
    return inc

これを呼び出すと、自由変数を持つ関数counter_in()が返されます。そこで、CLOSUREを作成しました。人々はクロージャ関数と呼んでいますが、これは混乱を招くと思います。人々は「OK内部関数はクロージャだ」と考えています。実際にはクロージャーではありません。クロージャーの一部であるため、生活を楽にするために、クロージャー機能と呼ばれています。incinitial_valueincinc

  myClosingOverFunc=counter_in(2)

incこれは、自由変数を閉じている関数を返しますinitial_value。呼び出すときmyClosingOverFunc

 myClosingOverFunc() 

2を出力します。

Pythonは、クロージャシステムが存在することを確認すると、CELLという新しいobjを作成します。これはinitial_value、この場合は自由変数の名前のみを格納します。このCellobjは、の値を格納する別のオブジェクトを指しますinitial_value

この例でinitial_valueは、外部関数と内部関数はこのセルオブジェクトを指し、このセルオブジェクトはの値を指しますinitial_value

  variable initial_value =====>> CELL ==========>> value of initial_value

したがって、counter_inそのスコープを呼び出すと、スコープはなくなりますが、それは問題ではありません。変数initial_valueがCELLオブジェクトを直接参照しているためです。の値を間接的に参照しますinitial_value。そのため、外部関数のスコープがなくなっても、内部関数は自由変数にアクセスできます。

関数を引数として受け取り、この関数が呼び出された回数を返す関数を作成するとします。

def counter(fn):
    # since cnt is a free var, python will create a cell and this cell will point to the value of cnt
    # every time cnt changes, cell will be pointing to the new value
    cnt = 0

    def inner(*args, **kwargs):
        # we cannot modidy cnt with out nonlocal
        nonlocal cnt
        cnt += 1
        print(f'{fn.__name__} has been called {cnt} times')
        # we are calling fn indirectly via the closue inner
        return fn(*args, **kwargs)
    return inner
      

この例cntでは、自由変数であり、inner+ cntCLOSUREを作成します。Pythonがこれを確認すると、CELL Objが作成され、cnt常にこのセルobjを直接参照し、CELLはの値を格納するメモリ内の別のobjを参照しますcnt。最初はcnt=0です。

 cnt   ======>>>>  CELL  =============>  0

パラメータを渡して内部関数を呼び出すcounter(myFunc)()と、cntが1増加するため、参照スキーマは次のように変更されます。

 cnt   ======>>>>  CELL  =============>  1  #first counter(myFunc)()
 cnt   ======>>>>  CELL  =============>  2  #second counter(myFunc)()
 cnt   ======>>>>  CELL  =============>  3  #third counter(myFunc)()

これは、クロージャの1つのインスタンスにすぎません。別の関数を渡すことで、クロージャの複数のインスタンスを作成できます

counter(differentFunc)()

これにより、上記とは異なるCELLobjが作成されます。別のクロージャーインスタンスを作成しました。

 cnt  ======>>  difCELL  ========>  1  #first counter(differentFunc)()
 cnt  ======>>  difCELL  ========>  2  #secon counter(differentFunc)()
 cnt  ======>>  difCELL  ========>  3  #third counter(differentFunc)()


  
于 2020-09-30T08:32:28.307 に答える
1

物事をより明確にするのに役立つ場合は、PythonとJSの例の別の簡単な比較を提供したいと思います。

JS:

function make () {
  var cl = 1;
  function gett () {
    console.log(cl);
  }
  function sett (val) {
    cl = val;
  }
  return [gett, sett]
}

と実行:

a = make(); g = a[0]; s = a[1];
s(2); g(); // 2
s(3); g(); // 3

Python:

def make (): 
  cl = 1
  def gett ():
    print(cl);
  def sett (val):
    cl = val
  return gett, sett

と実行:

g, s = make()
g() #1
s(2); g() #1
s(3); g() #1

理由:他の多くの人が上で述べたように、Pythonでは、内部スコープに同じ名前の変数への割り当てがある場合、内部スコープに新しい参照が作成されます。varキーワードで明示的に宣言しない限り、JSではそうではありません。

于 2017-06-30T11:45:46.530 に答える