web-dev-qa-db-ja.com

ネストされた関数pythonがクロージャーと呼ばれないのはなぜですか?

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

ネストされた関数は、外部世界で使用されないため、クロージャーではありませんか?

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

226

クロージャは、関数がその実行を終了した外側のスコープからローカル変数にアクセスしたときに発生します。

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

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

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

したがって、ネストされた関数が

  1. 囲みスコープにローカルなアクセス変数、
  2. それらがそのスコープ外で実行されたときにそうする、

それは閉鎖ではありません。

以下は、クロージャーではないネストされた関数の例です。

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

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

ここでは、値をパラメーターのデフォルト値にバインドしています。これは、関数printerが作成され、make_printerが戻った後、msgの外部のprinterの値への参照を維持する必要がない場合に発生します。 msgは、このコンテキストの関数printerの通常のローカル変数です。

361
aaronasterling

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

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

スニペットに来る前に:

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

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

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

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

  • pythonのすべての関数にはこのクロージャー属性がありますが、自由変数がない場合はコンテンツを保存しません。

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

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

NB: 無料 IS閉鎖を作成する必要があります。

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

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

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

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

>>> '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
 >>>

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

ここで、Closureを使用してFree Variableをすべて消去する別のスニペットを説明します。

>>> 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プロパティはクロージャーcellsのタプルであり、それらとそのコンテンツを明示的に参照できることがわかります-セルにはプロパティ「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'
>>>
89
James Sapam

Pythonには、weakクロージャーのサポートがあります。 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関数の変数スコープを管理する方法が原因です。内部関数はread外部関数の変数を使用できますが、writeは使用できません。

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

更新

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

1。globalキーワードを使用します(一般的にはお勧めしません)。

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

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

class Object(object):
    pass

initCounter内にObject scopeを作成して変数を保存します

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が示唆するように、xcounterのプロパティにすることができます

def initCounter ():

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

    counter.x = 0
    return counter
64
Cristian Garcia

Python 2にはクロージャーはありませんでした。類似クロージャーという回避策がありました。

既に与えられた答えには多くの例があります-変​​数を内部関数にコピーする、内部関数のオブジェクトを修正するなど.

Python 3では、サポートがより明確になり、簡潔になりました。

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

使用法:

start = closure()
start() # prints 1
start() # prints 2
start() # prints 3

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

12
Lee Benson

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

>>> 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
9
fp_mora
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

これは、クロージャーとは何か、どのように使用できるかの例です。

5
Krcn U

pythonとJSの例をもう1つの簡単な比較を提供したいと思います。

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ではそうではありません。

0
forumulator