code

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

codestyles 2020. 10. 9. 11:12
반응형

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


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

Here, when the lambda is called, it looks in the lambda' definition scope for a x. This x is a local variable defined in factory's body. Because of this, the value used on lambda execution will be the value that was passed as a parameter during the call to factory. And doremi!

As a note, I could have defined factory as factory(m) [replace x by m], the behavior is the same. I used a different name for clarity :)

You might find that Andrej Bauer got similar lambda problems. What's interesting on that blog is the comments, where you'll learn more about python closure :)


Not directly related to the issue at hand, but an invaluable piece of wisdom nevertheless: Python Objects by Fredrik Lundh.


First, what you are seeing is not a problem, and not related to call-by-reference or by-value.

The lambda syntax you defined has no parameters, and as such, the scope you are seeing with parameter m is external to the lambda function. This is why you are seeing these results.

Lambda syntax, in your example is not necessary, and you would rather be using a simple function call:

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

Again, you should be very precise about what lambda parameters you are using and where exactly their scope begins and ends.

As a side note, regarding parameter passing. Parameters in python are always references to objects. To quote Alex Martelli:

The terminology problem may be due to the fact that, in python, the value of a name is a reference to an object. So, you always pass the value (no implicit copying), and that value is always a reference. [...] Now if you want to coin a name for that, such as "by object reference", "by uncopied value", or whatever, be my guest. Trying to reuse terminology that is more generally applied to languages where "variables are boxes" to a language where "variables are post-it tags" is, IMHO, more likely to confuse than to help.


The variable m is being captured, so your lambda expression always sees its "current" value.

If you need to effectively capture the value at a moment in time, write a function takes the value you want as a parameter, and returns a lambda expression. At that point, the lambda will capture the parameter's value, which won't change when you call the function multiple times:

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()

Output:

do
re
mi

there are actually no variables in the classic sense in Python, just names that have been bound by references to the applicable object. Even functions are some sort of object in Python, and lambdas do not make an exception to the rule :)


As a side note, map, although despised by some well known Python figure, forces a construction which prevents this pitfall.

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

NB : the first lambda i acts like the factory in other answers.


Yes, that's a problem of scope, it binds to the outer m, whether you are using a lambda or a local function. Instead, use a functor:

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))

the soluiton to lambda is more lambda

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']

the outer lambda is used to bind the current value of i to j at the

each time the outer lambda is called it makes an instance of the inner lambda with j bound to the current value of i as i's value

참고URL : https://stackoverflow.com/questions/938429/scope-of-lambda-functions-and-their-parameters

반응형