람다 함수 및 매개 변수의 범위? 이벤트에 대해 거의 똑같은 콜백 함수가

일련의 GUI 이벤트에 대해 거의 똑같은 콜백 함수가 필요합니다. 함수는 어떤 이벤트가 호출했는지에 따라 약간 다르게 작동합니다. 나에게 간단한 경우처럼 보이지만 람다 함수의 이상한 동작을 이해할 수 없습니다.

그래서 아래에 다음과 같은 간단한 코드가 있습니다.

def callback(msg):
    print msg

#creating a list of function handles with an iterator
funcList=[]
for m in ('do', 're', 'mi'):
    funcList.append(lambda: callback(m))
for f in funcList:
    f()

#create one at a time
funcList=[]
funcList.append(lambda: callback('do'))
funcList.append(lambda: callback('re'))
funcList.append(lambda: callback('mi'))
for f in funcList:
    f()

이 코드의 출력은 다음과 같습니다.

mi
mi
mi
do
re
mi

기대했다:

do
re
mi
do
re
mi

반복자를 사용하면 왜 문제가 발생합니까?

딥 카피를 사용해 보았습니다.

import copy
funcList=[]
for m in ('do', 're', 'mi'):
    funcList.append(lambda: callback(copy.deepcopy(m)))
for f in funcList:
    f()

그러나 이것은 같은 문제가 있습니다.



답변

여기서 문제 m는 주변 범위에서 가져온 변수 (참조)입니다. 람다 범위에는 매개 변수 만 포함됩니다.

이 문제를 해결하려면 람다에 대한 다른 범위를 만들어야합니다.

def callback(msg):
    print msg

def callback_factory(m):
    return lambda: callback(m)

funcList=[]
for m in ('do', 're', 'mi'):
    funcList.append(callback_factory(m))
for f in funcList:
    f()

위의 예에서 람다는 주변 범위를 사용하여를 찾지 m만 이번에 callback_factorycallback_factory
호출 마다 한 번씩 생성되는 범위입니다 .

또는 functools.partial 사용 :

from functools import partial

def callback(msg):
    print msg

funcList=[partial(callback, m) for m in ('do', 're', 'mi')]
for f in funcList:
    f()

답변

람다가 생성 될 때 사용하는 둘러싸는 범위에있는 변수의 복사본을 만들지 않습니다. 나중에 변수 값을 조회 할 수 있도록 환경에 대한 참조를 유지합니다. 하나만 m있습니다. 루프를 통해 매번 할당됩니다. 루프 후 변수 m에는 value가 'mi'있습니다. 따라서 나중에 생성 한 함수를 실제로 실행하면 해당 함수를 생성 m한 환경에서의 값을 조회 할 것이며, 그때까지 value를 갖게 'mi'됩니다.

이 문제에 대한 일반적인 관용적 해결책 중 하나 m는 람다가 생성 된 시점 의 값을 선택적 매개 변수의 기본 인수로 사용하여 캡처하는 것입니다. 일반적으로 동일한 이름의 매개 변수를 사용하므로 코드 본문을 변경할 필요가 없습니다.

for m in ('do', 're', 'mi'):
    funcList.append(lambda m=m: callback(m))

답변

파이썬은 물론 참조를 사용하지만이 맥락에서는 중요하지 않습니다.

람다 (또는 정확히 동일한 동작이므로 함수)를 정의하면 런타임 전에 람다 식을 평가하지 않습니다.

# defining that function is perfectly fine
def broken():
    print undefined_var

broken() # but calling it will raise a NameError

람다 예보다 훨씬 더 놀랍습니다.

i = 'bar'
def foo():
    print i

foo() # bar

i = 'banana'

foo() # you would expect 'bar' here? well it prints 'banana'

간단히 말해서, 동적이라고 생각하십시오. 해석 전에는 아무것도 평가되지 않기 때문에 코드에서 m의 최신 값을 사용합니다.

람다 실행에서 m을 찾을 때 m은 최상위 범위에서 가져옵니다. 즉, 다른 사람들이 지적했듯이; 다른 범위를 추가하여 해당 문제를 피할 수 있습니다.

def factory(x):
    return lambda: callback(x)

for m in ('do', 're', 'mi'):
    funcList.append(factory(m))

여기에서 람다가 호출되면 람다의 정의 범위에서 x를 찾습니다. 이 x는 공장 본문에 정의 된 지역 변수입니다. 이 때문에 람다 실행에 사용되는 값은 팩토리 호출 중에 매개 변수로 전달 된 값이됩니다. 그리고 도레미!

참고로 factory (m) [x를 m으로 대체]로 factory를 정의 할 수 있었지만 동작은 동일합니다. 명확성을 위해 다른 이름을 사용했습니다. 🙂

Andrej Bauer 가 비슷한 람다 문제를 가지고 있음을 알 수 있습니다 . 그 블로그에서 흥미로운 점은 파이썬 클로저에 대해 더 많이 배울 수있는 코멘트입니다. 🙂


답변

당면한 문제와 직접적인 관련이 없지만 그럼에도 불구하고 귀중한 지혜의 조각 : Fredrik Lundh의 Python Objects .


답변

예, 그것은 범위의 문제입니다. 람다를 사용하든 로컬 함수를 사용하든 외부 m에 바인딩됩니다. 대신 펑터를 사용하세요 :

class Func1(object):
    def __init__(self, callback, message):
        self.callback = callback
        self.message = message
    def __call__(self):
        return self.callback(self.message)
funcList.append(Func1(callback, m))

답변

솔루이 톤에서 람다로의 변환은 더 람다입니다.

In [0]: funcs = [(lambda j: (lambda: j))(i) for i in ('do', 're', 'mi')]

In [1]: funcs
Out[1]:
[<function __main__.<lambda>>,
 <function __main__.<lambda>>,
 <function __main__.<lambda>>]

In [2]: [f() for f in funcs]
Out[2]: ['do', 're', 'mi']

아우터 lambda의 현재 값에 결합하는 데 사용되는 i발을 j
상기

외측마다 lambda그것을 호출은 내부의 인스턴스를 만든다 lambda으로 j의 전류 값에 바인딩 i으로 i의 값


답변

첫째, 당신이보고있는 것은 문제가 아니며 참조 별 또는 값별 호출과 관련이 없습니다.

정의한 람다 구문에는 매개 변수가 없으므로 매개 변수로 표시되는 범위 m는 람다 함수 외부에 있습니다. 이것이 바로 이러한 결과를 보는 이유입니다.

예제에서 Lambda 구문은 필요하지 않으며 간단한 함수 호출을 사용하는 것이 좋습니다.

for m in ('do', 're', 'mi'):
    callback(m)

다시 말하지만, 사용중인 람다 매개 변수와 해당 범위가 정확히 시작되고 끝나는 위치에 대해 매우 정확해야합니다.

부수적으로 매개 변수 전달과 관련하여. 파이썬의 매개 변수는 항상 객체에 대한 참조입니다. Alex Martelli를 인용하려면 :

용어 문제는 파이썬에서 이름의 값이 객체에 대한 참조라는 사실 때문일 수 있습니다. 따라서 항상 값 (암시 적 복사 없음)을 전달하고 해당 값은 항상 참조입니다. […] 이제 “객체 참조로”, “복사되지 않은 값으로”, 또는 무엇이든 이름을 만들려면 내 손님이 되십시오. “변수가 상자”인 언어에 “변수가 포스트잇 태그”인 언어에보다 일반적으로 적용되는 용어를 재사용하려고하면 IMHO가 도움이되기보다 혼동 될 가능성이 더 큽니다.