람다 함수 및 매개 변수의 범위?


89

일련의 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()

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


3
질문 제목이 다소 오해의 소지가 있습니다.
lispmachine

1
람다가 헷갈 린다면 왜 사용하나요? def를 사용하여 함수를 정의하지 않는 이유는 무엇입니까? 람다를 중요하게 만드는 문제는 무엇입니까?
S.Lott

@ S. 로트 중첩 된 기능은 동일한 문제에 (아마도 더 명확하게 볼)가 발생합니다
lispmachine

1
@agartland : 나야? 나도 GUI 이벤트에 대해 작업하고 있었고 백그라운드 조사
중에이

5
다른 값을 가진 루프에 정의 된 람다가 모두 동일한 결과를 반환하는 이유를 참조하세요 . Python에 대한 공식 프로그래밍 FAQ에서. 문제를 아주 잘 설명하고 해결책을 제공합니다.
abarnert 2014 년

답변:


79

여기서 문제 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()

2
이 설명은 약간 오해의 소지가 있습니다. 문제는 범위가 아니라 반복에서 m 값의 변화입니다.
Ixx

위의 주석은 페 노니 몬과 해결책을 설명하는 링크가 제공되는 질문에 대한 주석에서 @abarnert가 언급 한대로 사실입니다. 팩토리 메서드는 팩토리 메서드에 대한 인수와 동일한 효과를 제공하며 람다에 로컬 범위가있는 새 변수를 만드는 효과가 있습니다. 그러나 주어진 솔리 션은 람다에 대한 인수가 없기 때문에 구문 적으로 작동하지 않습니다. 아래 람다 솔루션의 람다는 람다를 만드는 새로운 영구 메서드를 만들지 않고도 동일한 효과를 제공합니다
Mark Parris

132

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

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

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

나는 기본값을 가진 선택적 매개 변수 를 의미 합니다
lispmachine

6
좋은 해결책! 까다 롭지 만 원래의 의미가 다른 구문보다 명확하다고 느낍니다.
Quantum7

3
이것에 대해 전혀 괴롭거나 까다로운 것은 없습니다. 공식 Python FAQ에서 제안하는 것과 똑같은 솔루션입니다. 를 참조하십시오 여기 .
abarnert

3
@abernert, "hackish and tricky"는 "공식 Python FAQ가 제안하는 솔루션"과 반드시 ​​호환되지 않는 것은 아닙니다. 참조 해 주셔서 감사합니다.
Don Hatch

1
이 개념에 익숙하지 않은 사람에게는 동일한 변수 이름을 재사용하는 것이 명확하지 않습니다. 람다 n = m이면 ​​그림이 더 좋을 것입니다. 예, 콜백 매개 변수를 변경해야하지만 for 루프 본문은 제 생각과 동일하게 유지 될 수 있습니다.
Nick

6

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

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

# 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 가 비슷한 람다 문제를 가지고 있음을 알 수 있습니다 . 그 블로그에서 흥미로운 점은 파이썬 클로저에 대해 더 많이 배울 수있는 코멘트입니다. :)


1

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


1
답변과 직접 관련이 없지만 새끼 고양이 검색 : google.com/search?q=kitten
Singletoned

@Singletoned : OP가 내가 링크를 제공 한 기사를 모색했다면, 그들은 처음에 질문을하지 않을 것입니다. 그것이 간접적으로 관련된 이유입니다. 고양이가 어떻게 내 대답과 간접적으로 관련이 있는지 설명해 주시면 기뻐하실 것입니다 (전체 론적 접근 방식을 통해 나는 추측합니다;)
tzot

1

예, 그것은 범위의 문제입니다. 람다를 사용하든 로컬 함수를 사용하든 외부 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))

1

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

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의 값


0

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

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

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

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

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

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

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


0

변수 m가 캡처되고 있으므로 람다 식은 항상 "현재"값을 확인합니다.

한 순간에 값을 효과적으로 캡처해야하는 경우 원하는 값을 매개 변수로 사용하고 람다 식을 반환하는 함수를 작성하십시오. 이 시점에서 람다는 매개 변수의 값을 캡처합니다. 값은 함수를 여러 번 호출 할 때 변경되지 않습니다.

def callback(msg):
    print msg

def createCallback(msg):
    return lambda: callback(msg)

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

산출:

do
re
mi

0

실제로 Python에는 고전적인 의미의 변수가 없으며 해당 객체에 대한 참조로 묶인 이름 만 있습니다. 함수조차도 파이썬에서 일종의 객체이며 람다는 규칙에 예외를 두지 않습니다. :)


"고전적인 의미에서"라고 말하면 "C처럼"을 의미합니다. 파이썬을 포함하여 언어의 많은, C. 다르게 변수를 구현하는이
네드 BATCHELDER

0

부수적으로 map, 비록 잘 알려진 파이썬 인물에 의해 멸시 되기는하지만,이 함정을 막는 구조를 강요합니다.

fs = map (lambda i: lambda: callback (i), ['do', 're', 'mi'])

NB : 첫 번째 lambda i는 다른 답변에서 공장처럼 작동합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.