Programing

루프에서 함수 만들기

lottogame 2020. 10. 24. 09:26
반응형

루프에서 함수 만들기


루프 내부에 함수를 만들려고합니다.

functions = []

for i in range(3):
    def f():
        return i

    # alternatively: f = lambda: i

    functions.append(f)

문제는 모든 기능이 동일하게된다는 것입니다. 0, 1, 2를 반환하는 대신 세 함수 모두 2를 반환합니다.

print([f() for f in functions])
# expected output: [0, 1, 2]
# actual output:   [2, 2, 2]

왜 이런 일이 발생하고 각각 0, 1, 2를 출력하는 3 개의 다른 함수를 얻으려면 어떻게해야합니까?


지연 바인딩에 문제가 있습니다. 각 함수는 i가능한 한 늦게 조회됩니다 (따라서 루프가 끝난 후 호출 i되면로 설정됩니다 2).

초기 바인딩을 강제하여 쉽게 고칠 수 있습니다. 다음 def f():def f(i=i):같이 변경 하십시오.

def f(i=i):
    return i

기본값 (오른쪽 i에서 i=i인수 이름에 대한 기본값입니다 i왼쪽이다, i에서가 i=i)에서 조회됩니다 def하지에 시간 call시간, 그래서 본질적으로 그들은 특히 초기 바인딩을 찾고있는 방법이야.

f추가 인수 받는 것이 걱정된다면 (따라서 잠재적으로 잘못 호출 될 수 있음) 클로저를 "함수 팩토리"로 사용하는 더 정교한 방법이 있습니다.

def make_f(i):
    def f():
        return i
    return f

루프 f = make_f(i)에서 def대신 사용하십시오 .


설명

여기서 문제 i는 함수 f가 생성 될 때의 값이 저장되지 않는다는 것입니다. 오히려, f의 값 조회 i가 될 때 호출을 .

생각해 보면이 동작이 완벽하게 이해됩니다. 사실 함수가 작동 할 수있는 유일한 합리적인 방법입니다. 다음과 같이 전역 변수에 액세스하는 함수가 있다고 가정 해보십시오.

global_var = 'foo'

def my_function():
    print(global_var)

global_var = 'bar'
my_function()

이 코드를 읽으면 global_var함수가 선언 된 후의 이 변경 되었기 때문에 "foo"가 아닌 "bar"가 인쇄 될 것으로 예상 할 수 있습니다 . 자신의 코드에서도 똑같은 일이 발생합니다.를 호출 할 때 f의 값 i이 변경되고로 설정되었습니다 2.

해결책

실제로이 문제를 해결하는 방법에는 여러 가지가 있습니다. 다음은 몇 가지 옵션입니다.

  • i기본 인수로 사용하여 초기 바인딩 강제

    클로저 변수 (예 :)와 달리 i기본 인수는 함수가 정의 될 때 즉시 평가됩니다.

    for i in range(3):
        def f(i=i):  # <- right here is the important bit
            return i
    
        functions.append(f)
    

    To give a little bit of insight into how/why this works: A function's default arguments are stored as an attribute of the function; thus the current value of i is snapshotted and saved.

    >>> i = 0
    >>> def f(i=i):
    ...     pass
    >>> f.__defaults__  # this is where the current value of i is stored
    (0,)
    >>> # assigning a new value to i has no effect on the function's default arguments
    >>> i = 5
    >>> f.__defaults__
    (0,)
    
  • Use a function factory to capture the current value of i in a closure

    The root of your problem is that i is a variable that can change. We can work around this problem by creating another variable that is guaranteed to never change - and the easiest way to do this is a closure:

    def f_factory(i):
        def f():
            return i  # i is now a *local* variable of f_factory and can't ever change
        return f
    
    for i in range(3):           
        f = f_factory(i)
        functions.append(f)
    
  • Use functools.partial to bind the current value of i to f

    functools.partial lets you attach arguments to an existing function. In a way, it too is a kind of function factory.

    import functools
    
    def f(i):
        return i
    
    for i in range(3):    
        f_with_i = functools.partial(f, i)  # important: use a different variable than "f"
        functions.append(f_with_i)
    

Caveat: These solutions only work if you assign a new value to the variable. If you modify the object stored in the variable, you'll experience the same problem again:

>>> i = []  # instead of an int, i is now a *mutable* object
>>> def f(i=i):
...     print('i =', i)
...
>>> i.append(5)  # instead of *assigning* a new value to i, we're *mutating* it
>>> f()
i = [5]

Notice how i still changed even though we turned it into a default argument! If your code mutates i, then you must bind a copy of i to your function, like so:

  • def f(i=i.copy()):
  • f = f_factory(i.copy())
  • f_with_i = functools.partial(f, i.copy())

참고URL : https://stackoverflow.com/questions/3431676/creating-functions-in-a-loop

반응형