본문 바로가기

Python/중급

[Python] Closure

Closure(클로저) 사용 이유

  • 서버 프로그래밍 -> 동시성(Concurrency)제어 -> 메모리 공간에 여러 자원이 접근 -> 교착상태(Dead Lock)
  • 메모리를 공유하지 않고 메시지 전달로 처리하기 함
  • 클로저는 공유하되 변경되지 않는(Immutable, Read Only) 적극적으로 사용 -> 함수형 프로그래밍
  • 클로저는 불변자료구조 및 atom, STM -> 멀티스레드(Coroutine) 프로그래밍에 강점

 

클래스를 이용한 closure 구현

 

# 클래스 이용한 클로저 개념
class Averager():
    def __init__(self):
        self._series = []

    def __call__(self, v): # 클래스를 함수처럼 호출 가능.
        self._series.append(v)
        print('inner >>> {} / {}'.format(self._series, len(self._series)))
        return sum(self._series) / len(self._series)


# 인스턴스 생성
averager_cls = Averager()

# 누적
print(averager_cls(15))
print(averager_cls(35))
print(averager_cls(40))

print()
print(dir(averager_cls))

--------------------------------------------[result]

inner >>> [15] / 1
15.0
inner >>> [15, 35] / 2
25.0
inner >>> [15, 35, 40] / 3
30.0

['__call__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_series']

 

클로저 구현 예시

  • 외부에서 호출된 함수의 변수값, 상태(레퍼런스) 복사후, 저장 -> 후에 접근(엑세스) 가능
# 클로저(Closure) 사용
def closure_ex1():
    # Free variable
    series = []
    # 클로저 영역
    def averager(v):
        # series = [] # 주석 해제 후 확인
        series.append(v)
        print('inner >>> {} / {}'.format(series, len(series)))
        return sum(series) / len(series)
    
    return averager


avg_closure1 = closure_ex1()

print(avg_closure1)
print(avg_closure1(15))
print(avg_closure1(35))
print(avg_closure1(40))

--------------------------------------------[result]

<function closure_ex1.<locals>.averager at 0x000002AE9AA08B70>
inner >>> [15] / 1
15.0
inner >>> [15, 35] / 2
25.0
inner >>> [15, 35, 40] / 3
30.0
# function inspection

print(dir(avg_closure1))
print('***********************************************************')
print(dir(avg_closure1.__code__))
print('***********************************************************')
print(avg_closure1.__code__.co_freevars)  # ('series',)
print('***********************************************************')
# 외부에서 호출된 함수의 변수값, 상태(레퍼런스) 복사후, 저장 -> 후에 접근(엑세스) 가능
print(dir(avg_closure1.__closure__[0]))
print('***********************************************************')
print(avg_closure1.__closure__[0].cell_contents)

--------------------------------------------[result]


['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
***********************************************************
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_kwonlyargcount', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_stacksize', 'co_varnames']
***********************************************************
('series',)
***********************************************************
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'cell_contents']
***********************************************************
[15, 35, 40]

 

클로저를 잘못 구현한 예시

 

# 잘못된 클로저 사용
def closure_ex2():
    # Free variable
    cnt = 0
    total = 0

    def averager(v):
        cnt += 1 # cnt = cnt + 1
        total += v
        return total / cnt
    
    return averager

avg_closure2 = closure_ex2()

print(avg_closure2(15)) # 예외

--------------------------------------------[result]

---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-46-655eb182c9e0> in <module>
     14 avg_closure2 = closure_ex2()
     15 
---> 16 print(avg_closure2(15)) # 예외

<ipython-input-46-655eb182c9e0> in averager(v)
      6 
      7     def averager(v):
----> 8         cnt += 1 # cnt = cnt + 1
      9         total += v
     10         return total / cnt

UnboundLocalError: local variable 'cnt' referenced before assignment

 

클로저를 정상 구현한 예시

 

# Nonlocal -> Free variable
def closure_ex3():
    # Free variable
    cnt = 0
    total = 0
    
    def averager(v):
        nonlocal cnt, total # <== 이 부분이 중요함
        cnt += 1
        total += v
        print('>>>', total, cnt)
        return total / cnt
    
    return averager

avg_closure3 = closure_ex3()

print(avg_closure3(15))
print(avg_closure3(35))
print(avg_closure3(40))

print()
print(avg_closure3.__code__.co_freevars)  # ('series',)
# 외부에서 호출된 함수의 변수값, 상태(레퍼런스) 복사후, 저장 -> 후에 접근(엑세스) 가능
print(dir(avg_closure3.__closure__[0]))
print(len(avg_closure3.__closure__))
print(avg_closure3.__closure__[0].cell_contents)
print(avg_closure3.__closure__[1].cell_contents)

--------------------------------------------[result]

>>> 15 1
15.0
>>> 50 2
25.0
>>> 90 3
30.0

('cnt', 'total')
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'cell_contents']
2
3
90

 

코루틴 예외 처리 만들기 - 강제 예외처리 발생시키기

 

def sum_coroutine():
    try:
        total = 0
        while True:
            x = (yield)
            total += x
    except RuntimeError as e:
        print(e)
        yield total    # 코루틴 바깥으로 값 전달
 
co = sum_coroutine()
next(co)
 
for i in range(20):
    co.send(i)
 
print(co.throw(RuntimeError, '예외로 코루틴 끝내기')) # 190, 코루틴의 except에서 yield로 전달받은 값

---------------------- [실행결과]

예외로 코루틴 끝내기
190

 

'Python > 중급' 카테고리의 다른 글

[Python] 병행성(Concurrency) - basic  (0) 2021.05.25
[Python] Decorator  (0) 2021.05.25
[Python] 일급함수  (0) 2021.05.24
[Python] 고급 - Dict 및 Set(2)  (0) 2021.05.24
[Python] 고급 - Dict 및 Set(1)  (0) 2021.05.23