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가 변이 되고 클로저는 모두 동일한 i를 참조합니다 .
함수 creater를 만들고 호출 – 여기에 내가 가지고 올 수있는 최고의 솔루션입니다 그 대신. 이렇게하면 생성 된 각 기능 에 대해 서로 다른 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가 캡처되고 함수가 호출 될 때 바인딩 된 값을 반환한다는 것입니다. 기능적 언어에서는 이러한 상황이 결코 발생하지 않습니다. 그러나 파이썬과 함께 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는 더 이상 부모 범위의 것이 아니라 자체 범위의 새로운 변수 (예 : 람다에 대한 매개 변수)이므로 관찰 한 동작을 얻습니다. 파이썬에는이 암시적인 새로운 범위가 없으므로 for 루프의 본문은 i 변수를 공유합니다.
답변
나는 왜 아직도 어떤 언어에서는 이것이 어떤 식 으로든 어떤 식 으로든 다른 방식으로 작동하는지 완전히 확신하지 못합니다. Common Lisp에서는 Python과 같습니다.
(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이며 반대 방향으로 작성 됨). Scheme에서는 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
변수에 바인딩된다는 것입니다 . 해결책 (해결 방법)은 각 함수 (또는 람다)에 대해 별도의 환경 (스택 프레임)을 만드는 것입니다.
t = [ (lambda x: lambda y : x*y)(x) for x in range(5)]
>>> t[1](2)
2
>>> t[2](2)
4