Programing

클로저를 설명 할 수 있습니까 (파이썬과 관련이 있기 때문에)?

crosscheck 2020. 10. 14. 07:30
반응형

클로저를 설명 할 수 있습니까 (파이썬과 관련이 있기 때문에)?


나는 폐쇄에 대해 많이 읽고 이해한다고 생각하지만 나 자신과 다른 사람들을 위해 그림을 흐리게하지 않고 누군가가 폐쇄를 가능한 한 간결하고 명확하게 설명 할 수 있기를 바랍니다. 나는 어디서 그리고 왜 그것을 사용하고 싶은지 이해하는 데 도움이 될 간단한 설명을 찾고 있습니다.


폐쇄에 대한 폐쇄

객체는 메소드가 첨부 된 데이터이고 클로저는 데이터가 첨부 된 함수입니다.

def make_counter():
    i = 0
    def counter(): # counter() is a closure
        nonlocal i
        i += 1
        return i
    return counter

c1 = make_counter()
c2 = make_counter()

print (c1(), c1(), c2(), c2())
# -> 1 2 1 2

간단합니다. 포함 범위에서 변수를 참조하는 함수, 잠재적으로 제어 흐름이 해당 범위를 떠난 후. 마지막 부분은 매우 유용합니다.

>>> def makeConstantAdder(x):
...     constant = x
...     def adder(y):
...         return y + constant
...     return adder
... 
>>> f = makeConstantAdder(12)
>>> f(3)
15
>>> g = makeConstantAdder(4)
>>> g(3)
7

12와 4는 각각 f와 g 내부에서 "사라졌다"는 점에 유의하십시오.이 기능은 f와 g가 적절한 클로저를 만드는 이유입니다.


이 거칠고 간결한 정의가 마음 에 듭니다 .

더 이상 활성화되지 않은 환경을 참조 할 수있는 기능입니다.

나는 추가 할 것이다

클로저를 사용하면 변수를 매개 변수로 전달하지 않고도 함수에 변수를 바인딩 할 수 있습니다 .

매개 변수를 허용하는 데코레이터는 클로저에 일반적으로 사용됩니다. 클로저는 이러한 종류의 "기능 팩토리"에 대한 일반적인 구현 메커니즘입니다. 나는 전략이 런타임에 데이터에 의해 수정 될 때 전략 패턴 에서 클로저를 사용하는 것을 자주 선택합니다 .

익명의 블록 정의를 허용하는 언어 (예 : Ruby, C #)에서는 새로운 새로운 제어 구조를 구현하는 데 클로저를 사용할 수 있습니다. 익명 블록의 부족은 Python의 클로저 한계 중 하나 입니다.


솔직히 말해서, 나는 "폐쇄"가 정확히 무엇인지 그리고 그것에 대해 "폐쇄"가 무엇인지에 대해 명확하지 않은 것을 제외하고는 클로저를 완벽하게 잘 이해합니다. 용어 선택의 논리를 찾는 것을 포기하는 것이 좋습니다.

어쨌든, 여기에 내 설명이 있습니다.

def foo():
   x = 3
   def bar():
      print x
   x = 5
   return bar

bar = foo()
bar()   # print 5

여기서 핵심 아이디어는 foo에서 반환 된 함수 객체가 'x'가 범위를 벗어 났고 없어야하지만 로컬 var 'x'에 대한 후크를 유지한다는 것입니다. 이 후크는 var 자체에 대한 것이지 var가 당시에 가졌던 값이 아니므로 bar가 호출되면 3이 아닌 5를 인쇄합니다.

또한 Python 2.x에는 제한된 클로저가 있음을 분명히하십시오. 'x = bla'를 작성하면 foo의 'x'에 할당되지 않고 bar에서 로컬 'x'를 선언하기 때문에 'bar'내부에서 'x'를 수정할 수있는 방법이 없습니다. . 이것은 Python의 assignment = declaration의 부작용입니다. 이 문제를 해결하기 위해 Python 3.0은 nonlocal 키워드를 도입했습니다.

def foo():
   x = 3
   def bar():
      print x
   def ack():
      nonlocal x
      x = 7
   x = 5
   return (bar, ack)

bar, ack = foo()
ack()   # modify x of the call to foo
bar()   # print 7

클로저가 무엇인지 설명하는 것과 동일한 맥락에서 트랜잭션이 사용되는 것을 들어 본 적이 없으며 실제로 여기에는 트랜잭션 의미가 없습니다.

외부 변수 (상수)를 "닫기"때문에 클로저라고합니다. 즉, 함수가 아니라 함수가 생성 된 환경의 인클로저입니다.

다음 예제에서 x를 변경 한 후 클로저 g를 호출하면 g가 x에 대해 닫히기 때문에 g 내의 x 값도 변경됩니다.

x = 0

def f():
    def g(): 
        return x * 2
    return g


closure = f()
print(closure()) # 0
x = 2
print(closure()) # 4

다음은 클로저에 대한 일반적인 사용 사례입니다-GUI 요소에 대한 콜백 (버튼 클래스를 서브 클래 싱하는 대신). 예를 들어, 버튼 누름에 대한 응답으로 호출되는 함수를 구성하고 클릭 처리에 필요한 상위 범위의 관련 변수를 "닫기"할 수 있습니다. 이렇게하면 동일한 초기화 함수에서 매우 복잡한 인터페이스를 연결하여 모든 종속성을 클로저에 구축 할 수 있습니다.


파이썬에서 클로저는 변수가 불변으로 바인딩 된 함수의 인스턴스입니다.

실제로 데이터 모델 은 함수의 __closure__속성 에 대한 설명에서이를 설명 합니다.

함수의 자유 변수에 대한 바인딩을 포함 하는 셀튜플 또는 없음 . 읽기 전용

이를 증명하려면 :

def enclosure(foo):
    def closure(bar):
        print(foo, bar)
    return closure

closure_instance = enclosure('foo')

분명히 우리는 이제 변수 name에서 가리키는 함수가 있다는 것을 알고 closure_instance있습니다. 표면 상으로는 객체를 사용하여 호출 bar하면 문자열 'foo'과 문자열 표현이 무엇이든 인쇄해야합니다 bar.

사실, 문자열 'foo' 함수의 인스턴스에 묶여 있으며, cell_contents속성의 튜플에있는 첫 번째 (유일한) 셀 속성 에 액세스하여 여기서 직접 읽을 수 있습니다 __closure__.

>>> closure_instance.__closure__[0].cell_contents
'foo'

별도로 셀 객체는 C API 문서에 설명되어 있습니다.

"셀"개체는 여러 범위에서 참조하는 변수를 구현하는 데 사용됩니다.

그리고 우리는 클로저의 사용법을 보여줄 수 있습니다 'foo'. 함수에 갇혀 있고 변하지 않습니다.

>>> closure_instance('bar')
foo bar
>>> closure_instance('baz')
foo baz
>>> closure_instance('quux')
foo quux

그리고 아무것도 바꿀 수 없습니다.

>>> closure_instance.__closure__ = None
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: readonly attribute

부분 기능

주어진 예제는 부분 함수로 클로저를 사용하지만 이것이 우리의 유일한 목표라면 동일한 목표를 다음과 같이 달성 할 수 있습니다. functools.partial

>>> from __future__ import print_function # use this if you're in Python 2.
>>> partial_function = functools.partial(print, 'foo')
>>> partial_function('bar')
foo bar
>>> partial_function('baz')
foo baz
>>> partial_function('quux')
foo quux

부분 함수 예제에 맞지 않는 더 복잡한 클로저도 있으며 시간이 허락하는 한 더 자세히 설명하겠습니다.


다음은 Python3 클로저의 예입니다.

def closure(x):
    def counter():
        nonlocal x
        x += 1
        return x
    return counter;

counter1 = closure(100);
counter2 = closure(200);

print("i from closure 1 " + str(counter1()))
print("i from closure 1 " + str(counter1()))
print("i from closure 2 " + str(counter2()))
print("i from closure 1 " + str(counter1()))
print("i from closure 1 " + str(counter1()))
print("i from closure 1 " + str(counter1()))
print("i from closure 2 " + str(counter2()))

# result

i from closure 1 101
i from closure 1 102
i from closure 2 201
i from closure 1 103
i from closure 1 104
i from closure 1 105
i from closure 2 202

# A Closure is a function object that remembers values in enclosing scopes even if they are not present in memory.

# Defining a closure

# This is an outer function.
def outer_function(message):
    # This is an inner nested function.
    def inner_function():
        print(message)
    return inner_function

# Now lets call the outer function and return value bound to name 'temp'
temp = outer_function("Hello")
# On calling temp, 'message' will be still be remembered although we had finished executing outer_function()
temp()
# Technique by which some data('message') that remembers values in enclosing scopes 
# even if they are not present in memory is called closures

# Output: Hello

폐쇄 기준은 다음과 같습니다.

  1. 중첩 된 함수가 있어야합니다.
  2. 중첩 함수는 바깥 쪽 함수에 정의 된 값을 참조해야합니다.
  3. 둘러싸는 함수는 중첩 된 함수를 반환해야합니다.

# Example 2
def make_multiplier_of(n): # Outer function
    def multiplier(x): # Inner nested function
        return x * n
    return multiplier
# Multiplier of 3
times3 = make_multiplier_of(3)
# Multiplier of 5
times5 = make_multiplier_of(5)
print(times5(3)) # 15
print(times3(2)) #  6

저에게 "클로저"는 생성 된 환경을 기억할 수있는 기능입니다. 이 기능을 사용하면 클로저 내에서 변수 나 메서드를 사용할 수 있습니다. 다른 방법으로 더 이상 존재하지 않거나 범위로 인해 사용할 수 없기 때문에 사용할 수 없습니다. Ruby에서이 코드를 살펴 보겠습니다.

def makefunction (x)
  def multiply (a,b)
    puts a*b
  end
  return lambda {|n| multiply(n,x)} # => returning a closure
end

func = makefunction(2) # => we capture the closure
func.call(6)    # => Result equal "12"  

it works even when both, "multiply" method and "x" variable,not longer exist. All because the closure capability to remember.


we all have used Decorators in python. They are nice examples to show what are closure functions in python.

class Test():
    def decorator(func):
        def wrapper(*args):
            b = args[1] + 5
            return func(b)
        return wrapper

@decorator
def foo(val):
    print val + 2

obj = Test()
obj.foo(5)

here final value is 12

Here, the wrapper function is able to access func object because wrapper is "lexical closure", it can access it's parent attributes. That is why, it is able to access func object.


I would like to share my example and an explanation about closures. I made a python example, and two figures to demonstrate stack states.

def maker(a, b, n):
    margin_top = 2
    padding = 4
    def message(msg):
        print('\n’ * margin_top, a * n, 
            ' ‘ * padding, msg, ' ‘ * padding, b * n)
    return message

f = maker('*', '#', 5)
g = maker('', '♥’, 3)
f('hello')
g(‘good bye!')

The output of this code would be as follows:

*****      hello      #####

      good bye!    ♥♥♥

Here are two figures to show stacks and the closure attached to the function object.

when the function is returned from maker

when the function is called later

When the function is called through a parameter or a nonlocal variable, the code needs local variable bindings such as margin_top, padding as well as a, b, n. In order to ensure the function code to work, the stack frame of the maker function which was gone away long ago should be accessible, which is backed up in the closure we can find along with the 'message's function object.


The best explanation I ever saw of a closure was to explain the mechanism. It went something like this:

Imagine your program stack as a degenerate tree where each node has only one child and the single leaf node is the context of your currently executing procedure.

Now relax the constraint that each node can have only one child.

If you do this, you can have a construct ('yield') that can return from a procedure without discarding the local context (i.e. it doesn't pop it off the stack when you return). The next time the procedure is invoked, the invocation picks up the old stack (tree) frame and continues executing where it left off.

참고URL : https://stackoverflow.com/questions/13857/can-you-explain-closures-as-they-relate-to-python

반응형