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


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.