어휘 폐쇄는 어떻게 작동합니까?
-
04-07-2019 - |
문제
JavaScript 코드의 어휘 폐쇄로 문제를 해결하는 동안 Python 에서이 문제를 겪었습니다.
flist = []
for i in xrange(3):
def func(x): return x * i
flist.append(func)
for f in flist:
print f(2)
이 예제는 마음으로 피합니다 lambda
. "4 4 4"를 인쇄합니다. 놀랍습니다. "0 2 4"를 기대할 것입니다.
이 동등한 Perl 코드는 다음과 같습니다.
my @flist = ();
foreach my $i (0 .. 2)
{
push(@flist, sub {$i * $_[0]});
}
foreach my $f (@flist)
{
print $f->(2), "\n";
}
"0 2 4"가 인쇄되었습니다.
차이점을 설명해 주시겠습니까?
업데이트:
문제 아니다 ~와 함께 i
글로벌입니다. 이것은 동일한 동작을 표시합니다.
flist = []
def outer():
for i in xrange(3):
def inner(x): return x * i
flist.append(inner)
outer()
#~ print i # commented because it causes an error
for f in flist:
print f(2)
주석은 라인이 보여 주듯이 i
그 시점에서 알려지지 않았습니다. 그래도 "4 4 4"를 인쇄합니다.
해결책
파이썬은 실제로 정의 된대로 작동합니다. 세 가지 별도의 기능 만들어졌지만 각각은 있습니다 그들이 정의한 환경의 폐쇄 -이 경우 글로벌 환경 (또는 루프가 다른 함수 내에 배치되는 경우 외부 기능의 환경). 그러나 이것은 정확히 문제입니다.이 환경에서는 나는 돌연변이된다, 그리고 폐쇄는 모두 같은 i를 참조하십시오.
다음은 제가 생각해 낼 수있는 최상의 솔루션입니다. 기능 크리터를 만들고 호출하십시오. 저것 대신에. 이것은 강요 될 것입니다 다른 환경 생성 된 각 기능에 대해 a 다른 i 각각.
flist = []
for i in xrange(3):
def funcC(j):
def func(x): return x * j
return func
flist.append(funcC(i))
for f in flist:
print f(2)
이것은 부작용과 기능적 프로그래밍을 혼합 할 때 발생합니다.
다른 팁
루프에 정의 된 함수는 동일한 변수에 계속 액세스합니다. i
가치가 바뀌는 동안. 루프의 끝에서 모든 함수는 동일한 변수를 가리 킵니다.이 변수는 루프의 마지막 값을 유지합니다. 효과는 예에서보고 된 것입니다.
평가하기 위해 i
그리고 그 값을 사용하면, 일반적인 패턴은 매개 변수 기본값으로 설정하는 것입니다. 매개 변수 기본값은 def
명령문이 실행되므로 루프 변수의 값이 동결됩니다.
다음은 예상대로 작동합니다.
flist = []
for i in xrange(3):
def func(x, i=i): # the *value* of i is copied in func() environment
return x * i
flist.append(func)
for f in flist:
print f(2)
다음은 다음을 사용하는 방법입니다 functools
도서관 (질문이 제기 될 때 확신 할 수 없었습니다).
from functools import partial
flist = []
def func(i, x): return x * i
for i in xrange(3):
flist.append(partial(func, i))
for f in flist:
print f(2)
예상대로 출력 0 2 4.
이거 봐요:
for f in flist:
print f.func_closure
(<cell at 0x00C980B0: int object at 0x009864B4>,)
(<cell at 0x00C980B0: int object at 0x009864B4>,)
(<cell at 0x00C980B0: int object at 0x009864B4>,)
이는 모두 동일한 I 변수 인스턴스를 가리키며 루프가 끝나면 값이 2입니다.
읽을 수있는 솔루션 :
for i in xrange(3):
def ffunc(i):
def func(x): return x * i
return func
flist.append(ffunc(i))
일어나고있는 것은 I가 캡처되고 함수가 호출 당시에 바인딩되는 값을 반환하고 있다는 것입니다. 기능적 언어에서는 반등하지 않기 때문에 이런 종류의 상황은 결코 발생하지 않습니다. 그러나 Python과 LISP에서 본 것처럼 더 이상 사실이 아닙니다.
계획 예제의 차이점은 DO 루프의 의미와 관련이 있습니다. 체계는 다른 언어와 마찬가지로 기존 I 바인딩을 재사용하는 대신 루프를 통해 매번 새로운 I 변수를 효과적으로 생성하고 있습니다. 루프 외부에서 생성 된 다른 변수를 사용하여 돌연변이하면 스키마에서 동일한 동작이 나타납니다. 루프를 다음과 같이 교체하십시오.
(let ((ii 1)) (
(do ((i 1 (+ 1 i)))
((>= i 4))
(set! flist
(cons (lambda (x) (* ii x)) flist))
(set! ii i))
))
구경하다 여기 이것에 대한 추가 논의를 위해.
편집] 아마도 그것을 설명하는 더 좋은 방법은 DO 루프를 다음 단계를 수행하는 매크로로 생각하는 것입니다.
- 루프의 본문에 의해 정의 된 단일 매개 변수 (i)를 복용하는 람다를 정의하십시오.
- I의 적절한 값을 매개 변수로 사용하는 람다의 즉시 호출.
즉. 아래 파이썬과 동일합니다.
flist = []
def loop_body(i): # extract body of the for loop to function
def func(x): return x*i
flist.append(func)
map(loop_body, xrange(3)) # for i in xrange(3): body
I는 더 이상 부모의 범위에서 나온 것이 아니라 자체 범위의 새로운 변수 (즉, Lambda에 대한 매개 변수)이므로 관찰하는 동작을 얻습니다. Python에는이 암시 적 새로운 범위가 없으므로 For Loop의 본문은 I 변수를 공유합니다.
나는 여전히 어떤 언어로서 이것이 어떤 방식으로, 그리고 다른 방법으로 작동하는지 완전히 확신하지 못한다. 일반적인 LISP에서는 파이썬과 같습니다.
(defvar *flist* '())
(dotimes (i 3 t)
(setf *flist*
(cons (lambda (x) (* x i)) *flist*)))
(dolist (f *flist*)
(format t "~a~%" (funcall f 2)))
"6 6 6"인쇄 (여기서 목록은 1에서 3에서 3까지, 반대 방향으로 내장되어 있음). 체계에서는 perl에서와 같이 작동합니다.
(define flist '())
(do ((i 1 (+ 1 i)))
((>= i 4))
(set! flist
(cons (lambda (x) (* i x)) flist)))
(map
(lambda (f)
(printf "~a~%" (f 2)))
flist)
인쇄 "6 4 2"
내가 이미 언급했듯이 JavaScript는 Python/CL 캠프에 있습니다. 여기에는 구현 결정이있는 것으로 보입니다. 여기에는 다른 언어가 뚜렷한 방식으로 접근합니다. 나는 결정이 정확히 무엇인지 이해하고 싶습니다.
문제는 모든 로컬 기능이 동일한 환경에 결합하여 동일하다는 것입니다. i
변하기 쉬운. 솔루션 (해결 방법)은 각 함수 (또는 Lambda)에 대해 별도의 환경 (스택 프레임)을 만드는 것입니다.
t = [ (lambda x: lambda y : x*y)(x) for x in range(5)]
>>> t[1](2)
2
>>> t[2](2)
4
변수 i
글로벌이며, 값은 매번 함수가 2입니다. f
호출됩니다.
나는 다음과 같이 당신의 행동을 구현하는 경향이 있습니다.
>>> class f:
... def __init__(self, multiplier): self.multiplier = multiplier
... def __call__(self, multiplicand): return self.multiplier*multiplicand
...
>>> flist = [f(i) for i in range(3)]
>>> [g(2) for g in flist]
[0, 2, 4]
업데이트에 대한 응답: 그것은 글로벌 성이 아닙니다 i
그 자체 이 동작을 일으키는 것은 F가 호출되는 시간에 비해 고정 된 값을 갖는 인클 링 범위의 변수라는 사실입니다. 두 번째 예에서는 값입니다 i
의 범위에서 가져옵니다 kkk
기능을하고 기능을 호출 할 때 아무것도 변하지 않습니다. flist
.
행동의 배후에 대한 추론은 이미 설명되었고 여러 솔루션이 게시되었지만 이것이 가장 피스닉이라고 생각합니다 (Python의 모든 것이 대상입니다!).
flist = []
for i in xrange(3):
def func(x): return x * func.i
func.i=i
flist.append(func)
for f in flist:
print f(2)
Claudiu의 대답은 기능 생성기를 사용하여 꽤 좋지만 Piro의 대답은 기본 값을 가진 "숨겨진"인수로 만들기 때문에 정직합니다 (잘 작동하지만 "Pythonic"은 아닙니다). .