일련의 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_factory
는 callback_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가 도움이되기보다 혼동 될 가능성이 더 큽니다.