๐ ์ด ํฌ์คํ ์์๋ Python์ closure์ decorator ๋ํด ์์๋ณด๊ฒ ์ต๋๋ค.
๐ฅ ํจ์์์ ๋ณ์์ ๋ฒ์
๐ฅ closure ๋?
๐ฅ decorator ๋?
โ๏ธ ์๋ ํจ์๋ NameError๊ฐ ๋ฐ์ํฉ๋๋ค. b๋ผ๋ ๋ณ์๊ฐ ์ธ์๋ก ๋ค์ด์ค์ง๋ ์์๊ณ , ํจ์ ๋ฐ์ด๋ ๋ด์์ ์ ์ธ๋์ง ์์๊ธฐ ๋๋ฌธ์ ์ฐธ์กฐํ ์ ์๊ธฐ ๋๋ฌธ์ ๋๋ค.
def my_func1(a): print(a) print(b) # my_func1(5) # NameError: name 'b' is not defined
โ๏ธ ์๋์ฒ๋ผ b๋ฅผ ํจ์ ๋ฐ์ ์ ์ธํ๋ค๋ฉด ํจ์๋ ๋ฌธ์ ์์ด ์๋์ํฌ ์ ์์ต๋๋ค. ํจ์๋ ํจ์ ๋ด๋ถ์์ ํด๋น ๋ณ์๋ฅผ ์ฐพ์ง ๋ชปํ ๊ฒฝ์ฐ, ์ธ๋ถ์์ ๊ฐ์ ์ฐธ์กฐํ๊ธฐ ๋๋ฌธ์ ๋๋ค.
b = 10 def my_func2(a): print(a) print(b) my_func2(5) # 5 10
โ๏ธ ํจ์ ๋ด๋ถ์์๋ ๋ณ์ b๋ฅผ ์ฐพ๊ณ ์๋๋ฐ, ํจ์ ๋ด๋ถ์ b๋ ์ ์ธํ๊ธฐ ์ ์ ์ฌ์ฉ๋๊ณ ์๊ณ , ํจ์ ๋ฐ์๋ b๋ผ๋ ๋ณ์๊ฐ ์กด์ฌํฉ๋๋ค. ์ด๋ด ๋ "UnboundLocalError" Error๊ฐ ๋ฐ์ํฉ๋๋ค.
โ๏ธ ์ฆ, ํจ์ ๋ด๋ถ์์ ํด๋น ๋ณ์๋ฅผ ๋จผ์ ์ฐพ๊ธฐ ๋๋ฌธ์ ํจ์๊ฐ ์คํ๋๋ Runtime ์, b๋ผ๋ ๋ณ์๋ ํจ์ ๋ด๋ถ์ ์ ์ธ๋์๋ค๋ ๊ฒ์ ์ธ์ํ์ง๋ง ๋ณ์ b๋ฅผ ์ ์ธํ๊ธฐ ์ ์ ์ฐธ์กฐํ๋ ค ํ๊ธฐ ๋๋ฌธ์ Error์ด ๋ฐ์ํ๋ ๊ฒ์
๋๋ค.
โ๏ธ ์ด ๋, ํจ์ ๋ฐ์ ๋ณ์ b๋ ์ฐธ์กฐ๋์ง ์์ต๋๋ค. Runtime ์, ํจ์ ๋ด๋ถ์ ํด๋น ๋ณ์๊ฐ ์กด์ฌํ๊ธฐ ๋๋ฌธ์ ํจ์ ์ค์ฝํ ๋ฐ์ ํ์ํ์ง ์์ต๋๋ค.
b = 10 # ๐ ์ฐธ์กฐ๋์ง ์์ต๋๋ค. def my_func3(a): print(a) print(b) b = 5 # ๐ b ๋ณ์ ์ ์ธ my_func3(5) # UnboundLocalError: local variable 'b' referenced before assignment
๐ค closure๋ ๋ฌด์์ผ๊น?
โ๏ธ ํด๋ก์ ๋ ๋ฐํ๋๋ ๋ด๋ถ ํจ์์ ๋ํด์ ์ ์ธ๋๋ ์ฐ๊ฒฐ ์ ๋ณด๋ฅผ ๊ฐ์ง๊ณ ์ฐธ์กฐํ๋ ๋ฐฉ์์ผ๋ก ๋ฐํ ๋น์ ํจ์ ์ ํจ๋ฒ์๋ฅผ ๋ฒ์ด๋ ๋ณ์ ๋๋ ๋งค์๋์ ์ง์ ์ ๊ทผ์ ๊ฐ๋ฅํ๊ฒ ํด์ฃผ๋ ๋ฐฉ๋ฒ์
๋๋ค.
โ๏ธ ๋ฐํ๋๋ ๋ด๋ถ ํจ์๋ ํจ์์์ ํจ์๊ฐ ์๋ค๋ ์๋ฏธ๋ก,, closure๋ ์ธ๋ถํจ์ , ๋ด๋ถํจ์์ธ ์ด 2๊ฐ ์ด์์ผ๋ก ํจ์๋ก ์ด๋ค์ ธ์์ต๋๋ค.
โ๏ธ ๋์ ๋ ๊ฐ์ ํ๊ท ์ ๊ตฌํด์ฃผ๋ Class๋ฅผ ์์ฑํด๋ณด๊ณ , ๊ทธ ๋ค์ closure ํจ์๋ฅผ ์์ฑํด๋ณด๊ฒ ์ต๋๋ค.
class MyAvg: def __init__(self): self._series = [] def __call__(self, v): # callable ์ฌ์ฉ self._series.append(v) return sum(self._series) / len(self._series) avg_cls = MyAvg() print(avg_cls(15)) # 15.0 ๐ 15 / 1 print(avg_cls(35)) # 25.0 ๐ (15+35) / 2 print(avg_cls(40)) # 30.0 ๐ (15+35+40) / 3
โ๏ธ ์ด์ closure๋ฅผ ์ด์ฉํด ์ ๊ธฐ๋ฅ์ ๊ตฌํํด๋ณด๊ฒ ์ต๋๋ค.
โ๏ธ ํจ์ "closure_avg1"๋ฅผ ํธ์ถํ์ฌ ๋ณ์ "avg_closure1"์ ํ ๋นํ๊ธฐ ๋๋ฌธ์ "avg_closure1"์๋ averager ํจ์๊ฐ ๋ด๊ฒจ ์์ต๋๋ค. ์ด์ ํจ์ ์์ฒด๊ฐ ๋ฐํ๋ฉ๋๋ค.
โ๏ธ ์ฆ, "avg_closure1"๋ฅผ ํจ์์ฒ๋ผ ํธ์ถํ๋ฉด, ์๋์ฒ๋ผ ๊ฐ์ด ๋์ ๋์ด ํ๊ท ์ ๊ตฌํ ์ ์์ต๋๋ค.
โ๏ธ ์ด๋ "closure_avg1"์ ํธ์ถ์ ํตํด ๋ฐํ๋ "avg_closure1"์ด Free variable ์์ญ์ series๋ฅผ ๊ณ์ ์ฐธ์กฐํ๊ณ ์๊ธฐ ๋๋ฌธ์
๋๋ค. ์ด๋ฅผ ํด๋ก์ ธ๋ผ๊ณ ํฉ๋๋ค.
โ๏ธ Free variable ์์ญ์ ์ธ๋ถํจ์์ ๋ด๋ถํจ์ ์ฌ์ด์ ์์ญ์ ๋ปํ๋ฉฐ, ์ด ๊ณณ์ด closure์ ์์ญ์
๋๋ค.
โ๏ธ ์ด ์์ญ์ ์ด๋ค ๋ณ์๋ ๋งค์๋๊ฐ closure๋๋์ง ํ์ธํ๋ ค๋ฉด, dirํจ์๋ฅผ ๊ฐ์ง๊ณ ํ์ธํด๋ณผ ์ ์์ต๋๋ค.
def closure_avg1(): # Free variable ์์ญ series = [] def averager(v): series.append(v) return sum(series) / len(series) return averager # ๐ averager ๋ฐํ avg_closure1 = closure_avg1() # ๐ closure_avg1 ํธ์ถ๋ก ๋ฐํ๋ averager๋ฅผ avg_closure1์ ํ ๋น print(avg_closure1) # <function closure_avg1.<locals>.averager at 0x7fb78e308af0> print(avg_closure1(15)) # 15.0 print(avg_closure1(35)) # 25.0 print(avg_closure1(40)) # 30.0 print(dir(avg_closure1.__code__.co_freevars)) # ('series', ) ๐ Free variable ์์ญ ํ์ธ
โ๏ธ "averager" ํจ์ ๋ด๋ถ์ series๋ฅผ ์ ์ธํ๋ฉด ์ด๋ป๊ฒ ๋ ๊น์?
โ๏ธ "averager" ๋ด๋ถ์ series๋ฅผ ์ฐธ์กฐํ๊ธฐ ๋๋ฌธ์ Free variabl ์์ญ์ series๋ฅผ ์ธ์ํ์ง ๋ชปํฉ๋๋ค. ์ด์ ๊ฐ์ ๋์ ์์ผ ํ๊ท ์ ๊ตฌํ์ง ๋ชปํ๊ณ ํ์ฌ parameter๋ก ๋ค์ด์จ ๊ฐ์ ๋ํด์๋ง ํ๊ท ์ ๊ตฌํฉ๋๋ค. ์ฆ, series ๋ณ์๋ ๊ณ์ ์ด๊ธฐํ ๋ฉ๋๋ค.
def closure_avg1(): # Free variable ์์ญ series = [] def averager(v): series = [] series.append(v) return sum(series) / len(series) return averager avg_closure1 = closure_avg1() print(avg_closure1) # <function closure_avg1.<locals>.averager at 0x7fb78e308af0> print(avg_closure1(15)) # 15.0 print(avg_closure1(35)) # 35.0 print(avg_closure1(40)) # 40.0
โ๏ธ local ๋ณ์ 'count'๋ฅผ ํ ๋น ์ ์ฐธ์กฐํ๋ คํ๋ค๋ "UnboundLocalError" ์๋ฌ๊ฐ ๋ฐ์ํฉ๋๋ค.
โ๏ธ ๋ด๋ถ ํจ์์ธ "averager" ๋ด์ "count"๋ ์ธ๋ถ ํจ์์ธ "closure_avg"์ "count"์ ์ด๋ฆ๋ง ๊ฐ์ ๋ค๋ฅธ ํจ์์
๋๋ค.
โ๏ธ ์ด์ ํ ๋น์ฐ์ฐ์๋ฅผ ์ฌ์ฉ ํ์ ๋, "closure_avg"์ ํจ์์ "count"๋ฅผ ์ฐธ์กฐํ ์ ์๊ธฐ ๋๋ฌธ์ ํ ๋น์ ์ ์ฐธ์กฐํ๊ณ ์๋ค๋ ์๋ฌ๋ฅผ ๋ฐํํฉ๋๋ค.
def closure_avg(): # Free variable count = 0 total = 0 # closure ์์ญ def averager(v): count += 1 total += v return total / count return averager avg_closure = closure_avg() print(avg_closure(15)) # UnboundLocalError: local variable 'count' referenced before assignment
โ๏ธ ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์๋ ์ธ๋ถํจ์์ธ "closure_avg"์ "count"์ ๋ด๋ถํจ์์ธ "averager"์ "count"๊ฐ ๊ฐ์ ํจ์๋ ๊ฒ์ ๋ช
์ํด์ฃผ์ด์ผ ํฉ๋๋ค.
โ๏ธ ์ฆ, ๋ ๋ณ์๊ฐ ๊ฐ์ ๋ณ์์ด๊ธฐ ๋๋ฌธ์ ์ฐธ์กฐํ ์ ์๋๋กํ๋ ค๋ฉด, nonlocal์ ์ฌ์ฉํฉ๋๋ค.
def closure_avg(): # Free variable count = 0 total = 0 # closure ์์ญ def averager(v): nonlocal count, total # ๐ count์ total์ด ์ง์ญ๋ณ์๊ฐ ์๋์ ๋ช ์ํฉ๋๋ค. count += 1 total += v return total / count return averager avg_closure = closure_avg() print(avg_closure(15)) # 15.0 print(avg_closure(35)) # 25.0 print(avg_closure(40)) # 30.0
โ๏ธ ์ธ๋ถํจ์์ ์กด์ฌํ๋ ๋ณ์๋ ๋งค์๋ ๋ฑ์ ๋ด๋ถํจ์๋ก ๋ฌผ๊ณ ๋ค์ด์ ์ฒ๋ฆฌํ๊ธฐ ์ํด.
โ๏ธ ๋ฐ์ฝ๋ ์ดํฐ ์ฐ๊ธฐ ์ํด.
โ๏ธ ๋ฐ์ฝ๋ ์ดํฐ๋ ์ค๋ณต์ ์ ๊ฑฐํ๊ณ , ์ฝ๋๋ฅผ ๋ ๊ฐ๊ฒฐํ๊ฒ ํ๊ธฐ ์ํด ์ฅ์์ฒ๋ผ ํจ์ ์์ ๋ถ์ด ์ฌ์ฉ๋ฉ๋๋ค.
โ๏ธ Python์์๋ ํด๋ก์ ธ๋ณด๋ค ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ฌธ๋ฒ์ด ๊ฐ๊ฒฐํ๊ณ , ์กฐํฉํด์ ์ฌ์ฉํ๊ธฐ ์ฉ์ดํฉ๋๋ค.
โ๏ธ ๋จ, ์๋ฌ๊ฐ ๋ฐ์๋๋ฉด, ๋ฐ์ ์ง์ ์ถ์ ์ด ๋ชจํธํ๊ธฐ ๋๋ฌธ์ ๋๋ฒ๊น
์ ์์ด์๋ ๋ค์ ์ด๋ ต๋ค๋ ๋จ์ ์ด ์์ต๋๋ค.
โ๏ธ ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ์ฌ์ฉํ์ง ์์ ๊ฒฝ์ฐ, ์ฌ๋ฌ ์ค๋ณต์ด ๋ฐ์ํ๊ณ ์ฝ๋๊ฐ ๊ฐ๊ฒฐํ์ง ์๊ฒ๋๋๋ฐ์,, ์์๋ฅผ ํตํด ํ์ธํด๋ณด๊ฒ ์ต๋๋ค.
โ๏ธ ์ด๋ค ํจ์๋ค์ด ์คํ๋ ๋๋ง๋ค ํด๋น ํจ์์ ์์์๊ฐ๋ฅผ ์ฒดํฌํ๋ ํจ์๋ฅผ ํ์ํ๋ค๊ณ ๊ฐ์ ํ๊ฒ ์ต๋๋ค.
โ๏ธ ์๋ ํจ์๋ ํด๋ก์ ธ๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ํจ์ ์์ ํจ์๊ฐ ์กด์ฌํ๊ณ , ์ธ๋ถํจ์๋ฅผ ์คํ์ํค๋ฉด ๋ด๋ถํจ์๋ฅผ ๋ฐํํฉ๋๋ค.
import time def perf_clock(func): def perf_clocked(*args): # ์์ ์๊ฐ st = time.perf_counter() result = func(*args) # ์ข ๋ฃ์๊ฐ et = time.perf_counter() - st # ํจ์๋ช name = func.__name__ # ๐ ํจ์์ด๋ฆ # ๋งค๊ฐ๋ณ์ arg_str = ",".join(repr(arg) for arg in args) # ๐ ๋งค๊ฐ๋ณ์ ์ด๋ฆ print( "Result : [%0.5fs] %s(%s) => %r" % (et, name, arg_str, result) ) # ๐ ์์์๊ฐ, ํจ์์ด๋ฆ, ๋งค๊ฐ๋ณ์, ํธ์ถ๊ฒฐ๊ณผ return result return perf_clocked
โ๏ธ ์๋๋ TESTํ ๊ฐ๋จํ ํจ์์ ๋๋ค.
# sleep ํจ์ def time_func(seconds): time.sleep(seconds) # sum ํจ์ def sum_func(*numbers): return sum(numbers) # factorial ํจ์ def fact_func(n): return 1 if n < 2 else n * fact_func(n - 1)
โ๏ธ ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ์ฌ์ฉํ์ง ์๊ณ ํจ์๋ฅผ ํธ์ถํด๋ณด๊ฒ ์ต๋๋ค.
โ๏ธ Free Variable์์ ์ธ๋ถ ํจ์("perf_clock")๋ก ๋ค์ด์จ "func"๋ฅผ ๋ฌผ๊ณ ์๊ธฐ ๋๋ฌธ์ ๋ด๋ถ ํจ์("perf_clocked")์์ ์ด๋ฅผ snapshot์ ํด๋๊ณ ์ ๊ทผ์ด ๊ฐ๋ฅํฉ๋๋ค.
non_deco1 = perf_clock(time_func) non_deco2 = perf_clock(sum_func) non_deco3 = perf_clock(fact_func) print(non_deco1, non_deco1.__code__.co_freevars) # <function perf_clock.<locals>.perf_clocked at 0x7fd29bd08c10> ('func',) print(non_deco2, non_deco2.__code__.co_freevars) # <function perf_clock.<locals>.perf_clocked at 0x7fd29bd08ca0> ('func',) print(non_deco3, non_deco3.__code__.co_freevars) # <function perf_clock.<locals>.perf_clocked at 0x7fd29bd08d30> ('func',) non_deco1(2) # Result : [2.00056s] time_func(2) => None non_deco2(100, 200, 300) # Result : [0.00000s] sum_func(100,200,300) => 600 non_deco3(10) # Result : [0.00002s] fact_func(10) => 3628800
โ๏ธ ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ์ฌ์ฉํ๋ฉด ๋ณด๋ค ๊ฐ๊ฒฐํฉ๋๋ค. ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ์ฌ์ฉํ ํจ์ ์์ ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ์ ์ธํฉ๋๋ค. ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ์ ์ธํ๋ฉด, ๋ฐ๋ก ์๋์๋ ํจ์์ ๋ฐ์ฝ๋ ์ดํฐ๊ฐ ์ ์ฉ๋ฉ๋๋ค.
โ๏ธ ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ์ ์ธํ์๋ค๋ฉด, ์ด์ ์คํ์ํฌ ํจ์๋ฅผ ๊ฐ๋จํ ํธ์ถ๋งํด์ฃผ๋ฉด๋ฉ๋๋ค. ํด๋น ํจ์๊ฐ ํธ์ถ๋ ๋ ์๋์ผ๋ก ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ๋ฐ๊ฒฌํ์ฌ ํด๋ก์ ธ ๊ธฐ๋ฅ์ ์ ์ฉ์์ผ์ค๋๋ค.
def perf_clock(func): def perf_clocked(*args): # ์์ ์๊ฐ st = time.perf_counter() result = func(*args) # ์ข ๋ฃ์๊ฐ et = time.perf_counter() - st # ํจ์๋ช name = func.__name__ # ๐ ํจ์์ด๋ฆ # ๋งค๊ฐ๋ณ์ arg_str = ",".join(repr(arg) for arg in args) # ๐ ๋งค๊ฐ๋ณ์ ์ด๋ฆ print( "Result : [%0.5fs] %s(%s) => %r" % (et, name, arg_str, result) ) # ๐ ์์์๊ฐ, ํจ์์ด๋ฆ, ๋งค๊ฐ๋ณ์, ํธ์ถ๊ฒฐ๊ณผ return result return perf_clocked # sleep ํจ์ @perf_clock # ๐ ๋ฐ์ฝ๋ ์ดํฐ ์ ์ธ def time_func(seconds): time.sleep(seconds) # sum ํจ์ @perf_clock # ๐ ๋ฐ์ฝ๋ ์ดํฐ ์ ์ธ def sum_func(*numbers): return sum(numbers) # factorial ํจ์ @perf_clock # ๐ ๋ฐ์ฝ๋ ์ดํฐ ์ ์ธ def fact_func(n): return 1 if n < 2 else n * fact_func(n - 1) time_func(2) # Result : [2.00031s] time_func(2) => None sum_func(100, 200, 300) # Result : [0.00000s] sum_func(100,200,300) => 600
โ๏ธ "fac_func" ํจ์๋ ์ฌ๊ทํจ์์ด๊ธฐ ๋๋ฌธ์ ์ฌ๊ทํ๋ ๊น์ด๋งํผ ๋ฐ์ฝ๋ ์ดํฐ๊ฐ ์คํ๋๋ ์ ๊ธฐํ ๋ชจ์ต์ ๋ณผ ์ ์์ต๋๋ค.
fact_func(10) """ Result : [0.00000s] fact_func(1) => 1 Result : [0.00002s] fact_func(2) => 2 Result : [0.00002s] fact_func(3) => 6 Result : [0.00003s] fact_func(4) => 24 Result : [0.00004s] fact_func(5) => 120 Result : [0.00005s] fact_func(6) => 720 Result : [0.00005s] fact_func(7) => 5040 Result : [0.00006s] fact_func(8) => 40320 Result : [0.00007s] fact_func(9) => 362880 Result : [0.00009s] fact_func(10) => 3628800 """