문제

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 루프를 다음 단계를 수행하는 매크로로 생각하는 것입니다.

  1. 루프의 본문에 의해 정의 된 단일 매개 변수 (i)를 복용하는 람다를 정의하십시오.
  2. 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"은 아닙니다). .

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top