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


249

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

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

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


8
흥미롭게도 일부 인터넷 검색에서 2006 년 12 월 effbot.org/zone/closure.htm 날짜를 발견 했습니다 . 확실하지 않습니다. "외부 복제본"이 너무 뿌려져 있습니까?
hbw

답변:


394

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

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입니다.


2
당신은 내 것보다 훨씬 낫습니다. 당신은 좋은 지적을합니다. 그러나 우리가 가장 엄격한 기능적 프로그래밍 정의로 가려고한다면, 당신의 예제는 기능입니까? 오랜 시간이 지났고 엄격한 함수형 프로그래밍이 값을 반환하지 않는 함수를 허용하는지 기억할 수 없습니다. 반환 값을 없음으로 간주하면 요점은 무의미하지만 다른 주제입니다.
mikerobi

6
@ mikerobi, 파이썬은 확실히 기능적인 언어가 아니기 때문에 함수형 프로그래밍을 고려해야한다고 확신하지 않습니다. 그러나 아닙니다. 내부 기능은 그 의미에서 기능이 아닙니다. 요점은 부작용을 만드는 것이기 때문입니다. 포인트도 보여주는 함수를 쉽게 만들 수 있습니다.
aaronasterling

31
@ mikerobi : 코드 덩어리가 폐쇄인지 여부는 코드가 아닌 환경에 따라 닫히는 지 여부에 달려 있습니다. 루틴, 함수, 프로 시저, 메소드, 블록, 서브 루틴 등 무엇이든 가능합니다. Ruby에서 메소드는 클로저가 될 수 없으며 블록 만 가능합니다. Java에서 메소드는 클로저가 될 수 없지만 클래스는 클로저가 될 수 있습니다. 그렇다고해서 그것들이 폐쇄되는 것은 아닙니다. ( 일부 변수 만 닫고 수정할 수 없다는 사실은 쓸모가 없습니다.) 방법은 단지 닫힌 절차라고 주장 할 수 self있습니다. (JavaScript / Python에서는 거의 사실입니다.)
Jörg W Mittag

3
@ JörgWMittag "닫기"를 정의하십시오.
Evgeni Sergeev

4
@EvgeniSergeev는 "닫는다", 즉 " i포괄 범위에서 지역 변수 (예 : ])를 나타냅니다 . 즉 i, 범위가 "실행을 완료 한 경우", 즉 프로그램의 실행이 코드의 다른 부분으로 진행된 경우에도 값을 검사 (또는 변경) 할 수 있습니다 . i정의 된 블록 은 더 이상 없지만 i여전히 참조하는 기능 은 그렇게 할 수 있습니다. 이것은 일반적으로 "변수 닫기"로 설명됩니다 i. 특정 변수를 처리하지 않으려면 해당 변수가 정의 된 전체 환경 프레임에서 닫는 것으로 구현할 수 있습니다.
Will Ness

103

이 질문은 이미 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'
>>>

9
Python 3에서는 다양한 속성 과 유사하게 func_closure이라고 합니다. __closure__func_*
lvc

3
또한 __closure_파이썬 3과의 호환성을 위해 2.6+ 파이썬에서 사용할 수 있습니다
피에르

폐쇄 는 함수 객체에 첨부 된 폐쇄 변수를 저장하는 레코드를 말합니다. 함수 자체가 아닙니다. 파이썬에서는 __closure__클로저 인 객체입니다.
Martijn Pieters

설명해 주셔서 감사합니다 @MartijnPieters.
James Sapam

71

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

27
이 문제를 해결할 수있는 방법이 있습니다. Python2에서는 x = [0]외부 범위 x[0] += 1에서 만들고 내부 범위에서 사용할 수 있습니다 . Python3에서는 코드를 그대로 유지하고 nonlocal 키워드를 사용할 수 있습니다 .
unutbu

"내부 함수는 외부 함수의 변수를 읽을 수 있지만 쓸 수는 없습니다." -unutbu의 의견에 따르면 정확하지 않습니다. 문제는 파이썬이 x = ...와 같은 것을 만나면 x는 로컬 변수로 해석되며 물론 아직 정의되지 않았습니다. OTOH, x가 변경 가능한 메소드를 가진 변경 가능한 객체 인 경우, 예를 들어 x가 자체를 변경하는 inc () 메소드를 지원하는 객체 인 경우 x.inc ()는 문제없이 작동합니다.
Thanh DK

@ThanhDK 변수에 쓸 수 없다는 것을 의미하지 않습니까? 변경 가능한 객체에서 메소드 호출을 사용하는 경우 자신에게 수정하도록 지시 하는 것만으로 실제로 변수를 수정하는 것이 아닙니다 (단지 객체에 대한 참조 만 보유 함). 다시 말해, 변수가 x가리키는 참조는 호출 inc()하거나 무엇이든 상관없이 정확하게 동일하게 유지 되며 변수에 효과적으로 쓰지 않습니다.
user193130

4
의 속성을 만드는xcounter # 2, imv보다 엄격히 좋은 또 다른 옵션이 있습니다 .
orome

9
파이썬 3에는 외부 함수의 변수 nonlocal와 유사 global하지만 키워드 가 있습니다. 이렇게하면 내부 함수가 외부 함수에서 이름을 리 바인드 할 수 있습니다. "이름에 바인딩"이 "변수 수정"보다 정확하다고 생각합니다.
leewz

16

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


1
참조를 위해, 흥미로운 : docs.python.org/3/reference/...를 . python3 문서에서 클로저에 대한 자세한 정보를 찾기가 왜 쉽지 않은지 (그리고 JS에서 올 때 어떻게 작동하는지 예상 할 수 있는지) 모르겠습니다.
user3773048

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

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

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


0

파이썬과 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에서는 그렇지 않습니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.