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


84

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

답변:


96

폐쇄에 대한 폐쇄

객체는 메서드가 연결된 데이터이고 클로저는 데이터가 연결된 함수입니다.

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

6
참고 nonlocal파이썬 3에서 추가되었습니다, 파이썬 2.X는하지 않았다 풀에, 읽기 - 쓰기 폐쇄 (즉 당신이 변수 닫혀 있지만, 그 값을 변경하지 읽을 수)
제임스 포터

6
@JamesPorter : 참고 : 당신은 에뮬레이션 할 수 있습니다 nonlocal, 변경 가능한 객체 등을 사용하여 파이썬 2 키워드 L = [0] \n def counter(): L[0] += 1; return L[0](이 경우 바인딩이 다른 개체에 대한) 즉, 당신은 이름을 변경할 수 없습니다 하지만 당신은 이름이 의미하는 가변 객체 자체를 변경할 수 있습니다 에. Python에서는 정수가 불변이므로 목록이 필요합니다.
jfs

1
@JFSebastian : 맞습니다. 즉, 항상 더러운 느낌, 더러운 해킹 :하지만
제임스 포터

46

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

>>> 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가 적절한 클로저를 만드는 이유입니다.


할 필요가 없습니다 constant = x. return y + x중첩 된 함수에서 수행 할 수 있으며 (또는 이름으로 인수를받을 수 있습니다. constant) 제대로 작동합니다. 인수는 비 인수 로컬과 다르지 않게 클로저에 의해 캡처됩니다.
ShadowRanger

15

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

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

나는 추가 할 것이다

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

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

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


15

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

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

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

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

또한, 그대로 g()계산 x * 2하지만 아무것도 반환하지 않습니다. 이어야합니다 return x * 2. 그럼에도 불구하고 "폐쇄"라는 단어에 대한 설명은 +1.
Bruno Le Floch 2012 년

3

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


2

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

실제로 데이터 모델 은 함수의 __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

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


2
# 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

1

다음은 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

0

저에게 "클로저"는 생성 된 환경을 기억할 수있는 기능입니다. 이 기능을 사용하면 클로저 내에서 변수 나 메서드를 사용할 수 있습니다. 다른 방법으로 더 이상 존재하지 않거나 범위로 인해 사용할 수 없기 때문에 사용할 수 없습니다. 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"  

"multiply"메소드와 "x"변수가 더 이상 존재하지 않는 경우에도 작동합니다. 클로저 기능이 기억하기 때문입니다.


0

우리 모두는 파이썬에서 데코레이터 를 사용 했습니다. 파이썬에서 클로저 함수가 무엇인지 보여주는 좋은 예입니다.

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)

여기서 최종 값은 12입니다.

여기서 래퍼 함수는 래퍼가 "어휘 폐쇄"이기 때문에 func 개체에 액세스 할 수 있으며 부모 속성에 액세스 할 수 있습니다. 그래서 func 객체에 접근 할 수 있습니다.


0

클로저에 대한 예제와 설명을 공유하고 싶습니다. 파이썬 예제와 스택 상태를 보여주는 두 개의 그림을 만들었습니다.

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

이 코드의 출력은 다음과 같습니다.

*****      hello      #####

      good bye!    ♥♥♥

다음은 스택과 함수 객체에 연결된 클로저를 보여주는 두 그림입니다.

메이커에서 함수가 반환 될 때

나중에 함수가 호출 될 때

함수가 매개 변수 또는 비 지역 변수를 통해 호출 될 때 코드에는 margin_top, padding 및 a, b, n과 같은 지역 변수 바인딩이 필요합니다. 함수 코드가 작동하는지 확인하려면 오래 전에 사라진 메이커 함수의 스택 프레임에 액세스 할 수 있어야합니다.이 스택 프레임은 '메시지의 함수 객체와 함께 찾을 수있는 클로저에 백업됩니다.


-2

내가 본 종결에 대한 가장 좋은 설명은 메커니즘을 설명하는 것입니다. 다음과 같이 진행되었습니다.

프로그램 스택을 각 노드에 자식이 하나만 있고 단일 리프 노드가 현재 실행중인 프로 시저의 컨텍스트 인 퇴화 트리라고 상상해보십시오.

이제 각 노드가 하나의 자식 만 가질 수 있다는 제약을 완화합니다.

이렇게하면 로컬 컨텍스트를 버리지 않고 프로 시저에서 반환 할 수있는 구성 ( 'yield')을 가질 수 있습니다 (즉, 반환 할 때 스택에서 팝하지 않음). 다음에 프로 시저가 호출 될 때 호출은 이전 스택 (트리) 프레임을 선택하고 중단 된 위치에서 계속 실행합니다.


그것은 폐쇄에 대한 설명이 아닙니다.
Jules

당신은 종결이 아니라 연속을 설명하고 있습니다.
Matthew Olenik
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.