TIL17. Python : Closure & Decorator

ID์งฑ์žฌยท2021๋…„ 9์›” 27์ผ
0

Python

๋ชฉ๋ก ๋ณด๊ธฐ
29/39
post-thumbnail

๐Ÿ“Œ ์ด ํฌ์ŠคํŒ…์—์„œ๋Š” Python์˜ closure์™€ decorator ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.



๐ŸŒˆ Closure & Decorator

๐Ÿ”ฅ ํ•จ์ˆ˜์—์„œ ๋ณ€์ˆ˜์˜ ๋ฒ”์œ„

๐Ÿ”ฅ closure ๋ž€?

๐Ÿ”ฅ decorator ๋ž€?



1. ํ•จ์ˆ˜์—์„œ ๋ณ€์ˆ˜์˜ ๋ฒ”์œ„

๐Ÿค” ์Šค์ฝ”ํ”„์— ๋ฒ”์œ„์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

โœ”๏ธ ์•„๋ž˜ ํ•จ์ˆ˜๋Š” 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


2. closure ๋ž€?

๐Ÿค” 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

๐Ÿค” ์ž˜๋ชป๋œ closure ์‚ฌ์šฉ ์˜ˆ์™€ nonlocal

โœ”๏ธ 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 

๐Ÿค” closure๋ฅผ ์™œ ์“ธ๊นŒ?

โœ”๏ธ ์™ธ๋ถ€ํ•จ์ˆ˜์— ์กด์žฌํ•˜๋Š” ๋ณ€์ˆ˜๋‚˜ ๋งค์„œ๋“œ ๋“ฑ์„ ๋‚ด๋ถ€ํ•จ์ˆ˜๋กœ ๋ฌผ๊ณ  ๋“ค์–ด์™€ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด.
โœ”๏ธ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ์“ฐ๊ธฐ ์œ„ํ•ด.



3. decorator ๋ž€?

๐Ÿค” ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์™œ ์“ธ๊นŒ?

โœ”๏ธ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š” ์ค‘๋ณต์„ ์ œ๊ฑฐํ•˜๊ณ , ์ฝ”๋“œ๋ฅผ ๋” ๊ฐ„๊ฒฐํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด ์žฅ์‹์ฒ˜๋Ÿผ ํ•จ์ˆ˜ ์œ„์— ๋ถ™์–ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
โœ”๏ธ 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  
"""  
profile
Keep Going, Keep Coding!

0๊ฐœ์˜ ๋Œ“๊ธ€