파이썬에서 중첩 함수를보고 사용했으며 클로저 정의와 일치합니다. 왜 nested functions
대신에 그들은 불려지 closures
는가?
중첩 함수는 외부 세계에서 사용되지 않기 때문에 클로저가 아닌가?
업데이트 : 클로저에 대해 읽고 있었고 파이썬과 관련 하여이 개념에 대해 생각하게했습니다. 아래 주석에서 누군가가 언급 한 기사를 검색하고 찾았지만 해당 기사의 설명을 완전히 이해할 수 없으므로이 질문을하는 이유입니다.
파이썬에서 중첩 함수를보고 사용했으며 클로저 정의와 일치합니다. 왜 nested functions
대신에 그들은 불려지 closures
는가?
중첩 함수는 외부 세계에서 사용되지 않기 때문에 클로저가 아닌가?
업데이트 : 클로저에 대해 읽고 있었고 파이썬과 관련 하여이 개념에 대해 생각하게했습니다. 아래 주석에서 누군가가 언급 한 기사를 검색하고 찾았지만 해당 기사의 설명을 완전히 이해할 수 없으므로이 질문을하는 이유입니다.
답변:
폐쇄는 함수가 실행을 완료 한 둘러싸는 범위에서 로컬 변수에 액세스 할 때 발생합니다.
def make_printer(msg):
def printer():
print msg
return printer
printer = make_printer('Foo!')
printer()
make_printer
호출 되면 printer
함수에 대한 컴파일 된 코드 가 상수 및 msg
로컬 값으로 스택에 새 프레임이 배치됩니다 . 그런 다음 함수를 작성하고 리턴합니다. 함수 printer
는 msg
변수를 참조 하므로 make_printer
함수가 반환 된 후에도 계속 유지됩니다 .
따라서 중첩 함수가 그렇지 않으면
그들은 폐쇄되지 않습니다.
다음은 클로저가 아닌 중첩 함수의 예입니다.
def make_printer(msg):
def printer(msg=msg):
print msg
return printer
printer = make_printer("Foo!")
printer() #Output: Foo!
여기서는 값을 매개 변수의 기본값에 바인딩합니다. 이것은 함수 printer
가 작성 될 때 발생 하므로 리턴 후에 msg
external 값에 대한 참조를 printer
유지할 필요가 없습니다 make_printer
. 이 문맥 msg
에서 함수의 일반적인 지역 변수 일뿐 printer
입니다.
self
있습니다. (JavaScript / Python에서는 거의 사실입니다.)
i
포괄 범위에서 지역 변수 (예 : ])를 나타냅니다 . 즉 i
, 범위가 "실행을 완료 한 경우", 즉 프로그램의 실행이 코드의 다른 부분으로 진행된 경우에도 값을 검사 (또는 변경) 할 수 있습니다 . i
정의 된 블록 은 더 이상 없지만 i
여전히 참조하는 기능 은 그렇게 할 수 있습니다. 이것은 일반적으로 "변수 닫기"로 설명됩니다 i
. 특정 변수를 처리하지 않으려면 해당 변수가 정의 된 전체 환경 프레임에서 닫는 것으로 구현할 수 있습니다.
이 질문은 이미 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'
>>>
func_closure
이라고 합니다. __closure__
func_*
__closure_
파이썬 3과의 호환성을 위해 2.6+ 파이썬에서 사용할 수 있습니다
__closure__
클로저 인 객체입니다.
파이썬은 클로저를 약하게 지원합니다. 무슨 뜻인지 확인하려면 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
x = [0]
외부 범위 x[0] += 1
에서 만들고 내부 범위에서 사용할 수 있습니다 . Python3에서는 코드를 그대로 유지하고 nonlocal 키워드를 사용할 수 있습니다 .
x
가리키는 참조는 호출 inc()
하거나 무엇이든 상관없이 정확하게 동일하게 유지 되며 변수에 효과적으로 쓰지 않습니다.
x
counter
# 2, imv보다 엄격히 좋은 또 다른 옵션이 있습니다 .
nonlocal
와 유사 global
하지만 키워드 가 있습니다. 이렇게하면 내부 함수가 외부 함수에서 이름을 리 바인드 할 수 있습니다. "이름에 바인딩"이 "변수 수정"보다 정확하다고 생각합니다.
파이썬 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에서는 그렇지 않습니다 .