[python] 파이썬 중첩 함수가 클로저라고 불리는 이유는 무엇입니까?

파이썬에서 중첩 함수를보고 사용했으며 클로저 정의와 일치합니다. 왜 nested functions대신에 그들은 불려지 closures는가?

중첩 함수는 외부 세계에서 사용되지 않기 때문에 클로저가 아닌가?

업데이트 : 클로저에 대해 읽고 있었고 파이썬과 관련 하여이 개념에 대해 생각하게했습니다. 아래 주석에서 누군가가 언급 한 기사를 검색하고 찾았지만 해당 기사의 설명을 완전히 이해할 수 없으므로이 질문을하는 이유입니다.



답변

폐쇄는 함수가 실행을 완료 한 둘러싸는 범위에서 로컬 변수에 액세스 할 때 발생합니다.

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가 작성 될 때 발생 하므로 리턴 후에 msgexternal 값에 대한 참조를 printer유지할 필요가 없습니다 make_printer. 이 문맥 msg에서 함수의 일반적인 지역 변수 일뿐 printer입니다.


답변

이 질문은 이미 aaronasterling 님 에 의해 답변되었습니다

그러나 누군가 변수가 후드 아래에 저장되는 방식에 관심이있을 수 있습니다.

스 니펫에 오기 전에 :

클로저는 엔 클로징 환경에서 변수를 상속하는 함수입니다. 함수 콜백을 I / O를 수행하는 다른 함수에 대한 인수로 전달하면이 콜백 함수가 나중에 호출되며이 함수는 거의 마술처럼 사용 가능한 모든 변수와 함께 선언 된 컨텍스트를 기억합니다. 그 맥락에서.

  • 함수가 자유 변수를 사용하지 않으면 클로저를 형성하지 않습니다.

  • 자유 변수를 사용하는 다른 내부 레벨이있는 ​​경우 모든 이전 레벨은 어휘 환경을 저장합니다 (예 : 끝에)

  • 기능 속성 func_closure파이썬 <3.x 또는 __closure__자유 변수 저장 파이썬> 3.X.

  • 파이썬의 모든 함수에는이 폐쇄 속성이 있지만 자유 변수가 없으면 내용을 저장하지 않습니다.

예 : 자유 변수가 없으므로 클로저 속성이 있지만 내용이 없습니다.

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

그리고 모든 파이썬 함수에는 클로저 속성이 있으므로 클로저 함수와 관련된 둘러싸는 변수를 살펴 보겠습니다.

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

위의 스 니펫에서는 프린터 함수 내부에 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'
>>>


답변

파이썬은 클로저를 약하게 지원합니다. 무슨 뜻인지 확인하려면 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

클로저는 이와 같이 작성된 함수에 “내부 메모리”기능을 제공하기 때문에 매우 우아합니다. 파이썬 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에서 구문 설탕을 제공 하는 함수 데코레이터 패턴 을 적어도 구현할 수 있습니다 .

최신 정보

지적했듯이 파이썬의 범위 제한을 처리하는 방법이 있으며 몇 가지를 공개하겠습니다.

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


답변

파이썬 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키워드를 감싸는 효과에 명시 적 언급 외부 변수 내부 기능을 결합한다. 따라서보다 명확하게 ‘폐쇄’입니다.


답변

별도의 영구 네임 스페이스가 필요한 상황이있었습니다. 나는 수업을 사용했다. 나는 그렇지 않다. 분리되었지만 영구적 인 이름은 폐쇄입니다.

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


답변

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

이것은 클로저가 무엇이며 어떻게 사용되는지에 대한 예입니다.


답변

파이썬과 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

파이썬 :

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

이유 : 위에서 많은 사람들이 말했듯이 파이썬에서는 내부 범위에 동일한 이름의 변수에 대입이 있으면 내부 범위에 새로운 참조가 작성됩니다. var키워드로 명시 적으로 선언하지 않는 한 JS에서는 그렇지 않습니다 .