어휘 폐쇄는 어떻게 작동합니까?
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)
부작용과 기능 프로그래밍을 혼합 할 때 발생하는 현상입니다.
The functions defined in the loop keep accessing the same variable i
while its value changes. At the end of the loop, all the functions point to the same variable, which is holding the last value in the loop: the effect is what reported in the example.
평가하기 위해 i
and use its value, a common pattern is to set it as a parameter default: parameter defaults are evaluated when the def
statement is executed, and thus the value of the loop variable is frozen.
다음은 예상대로 작동합니다.
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
library (which I'm not sure was available at the time the question was posed).
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)
Outputs 0 2 4, as expected.
이것 좀봐:
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>,)
It means they all point to the same i variable instance, which will have a value of 2 once the loop is over.
읽을 수있는 솔루션 :
for i in xrange(3):
def ffunc(i):
def func(x): return x * i
return func
flist.append(ffunc(i))
일어나고있는 일은 변수 i가 캡처되고 함수가 호출 될 때 바인딩 된 값을 반환한다는 것입니다. 기능적 언어에서는 이러한 상황이 결코 발생하지 않습니다. 그러나 파이썬과 함께 lisp에서 본 것처럼 이것은 더 이상 사실이 아닙니다.
The difference with your scheme example is to do with the semantics of the do loop. Scheme is effectively creating a new i variable each time through the loop, rather than reusing an existing i binding as with the other languages. If you use a different variable created external to the loop and mutate it, you'll see the same behaviour in scheme. Try replacing your loop with:
(let ((ii 1)) (
(do ((i 1 (+ 1 i)))
((>= i 4))
(set! flist
(cons (lambda (x) (* ii x)) flist))
(set! ii i))
))
Take a look here for some further discussion of this.
[Edit] Possibly a better way to describe it is to think of the do loop as a macro which performs the following steps:
- Define a lambda taking a single parameter (i), with a body defined by the body of the loop,
- An immediate call of that lambda with appropriate values of i as its parameter.
ie. the equivalent to the below python:
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
The i is no longer the one from the parent scope but a brand new variable in its own scope (ie. the parameter to the lambda) and so you get the behaviour you observe. Python doesn't have this implicit new scope, so the body of the for loop just shares the i variable.
I'm still not entirely convinced why in some languages this works one way, and in some another way. In Common Lisp it's like Python:
(defvar *flist* '())
(dotimes (i 3 t)
(setf *flist*
(cons (lambda (x) (* x i)) *flist*)))
(dolist (f *flist*)
(format t "~a~%" (funcall f 2)))
Prints "6 6 6" (note that here the list is from 1 to 3, and built in reverse"). While in Scheme it works like in 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)
Prints "6 4 2"
And as I've mentioned already, Javascript is in the Python/CL camp. It appears there is an implementation decision here, which different languages approach in distinct ways. I would love to understand what is the decision, exactly.
The problem is that all of the local functions bind to the same environment and thus to the same i
variable. The solution (workaround) is to create separate environments (stack frames) for each function (or lambda):
t = [ (lambda x: lambda y : x*y)(x) for x in range(5)]
>>> t[1](2)
2
>>> t[2](2)
4
The variable i
is a global, whose value is 2 at each time the function f
is called.
I would be inclined to implement the behavior you're after as follows:
>>> 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]
Response to your update: It's not the globalness of i
per se which is causing this behavior, it's the fact that it's a variable from an enclosing scope which has a fixed value over the times when f is called. In your second example, the value of i
is taken from the scope of the kkk
function, and nothing is changing that when you call the functions on flist
.
The reasoning behind the behavior has already been explained, and multiple solutions have been posted, but I think this is the most pythonic (remember, everything in Python is an object!):
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's answer is pretty good, using a function generator, but piro's answer is a hack, to be honest, as it's making i into a "hidden" argument with a default value (it'll work fine, but it's not "pythonic").
참고URL : https://stackoverflow.com/questions/233673/how-do-lexical-closures-work
'Programing' 카테고리의 다른 글
C #을 사용하여 파일에서 텍스트를 찾아 바꾸는 방법 (0) | 2020.06.17 |
---|---|
Javascript로 CSS를 어떻게 추가합니까? (0) | 2020.06.17 |
Bash Script-실행할 명령으로서 가변 컨텐츠 (0) | 2020.06.17 |
수정 된 파일 만`git status '할 수 있습니까? (0) | 2020.06.17 |
파이썬을 사용하여 '인쇄'출력을 파일로 리디렉션하는 방법은 무엇입니까? (0) | 2020.06.17 |