Fluent Python Ch07
( 출처 : 전문가를 위한 파이썬 / Fluent Python )
[ Function Decorators and Closures ]
Function decorators :
- “mark” functions in the source code, to enhance their behavior
1. Decorators 101
Decorator
- callable ( takes another function as argument )
[A] = [B]
[A]
@decorate
def target():
print('running target()')
[B]
def target():
print('running target()')
target = decorate(target)
Decorator usually replaces a function with a different one
- Decorator Function
- input : function
- output : function
def deco(func):
def inner():
print('running inner()')
return inner
@deco
def target():
print('running target()')
아래 코드 실행 시,
target()
함수의 print가 아닌,- 감싸고 있는
deco()
데코레이터에서의 print가 출력되는 것을 확인할 수 있다
>>> target()
running inner()
#-------------------------------------#
>>> target
<function deco.<locals>.inner at 0x10063b598>
2. When to use Decorators
registry = []
def register(func):
print('running register(%s)' % func)
registry.append(func)
return func
@register
def f1():
print('running f1()')
@register
def f2():
print('running f2()')
def f3():
print('running f3()')
def main():
print('running main()')
print('registry ->', registry)
f1()
f2()
f3()
if __name__=='__main__':
main()
$ python3 registration.py
#------------------------------------------------------#
running register(<function f1 at 0x100631bf8>) # 정의 되자마자 실행됨
running register(<function f2 at 0x100631c80>) # 정의 되자마자 실행됨
running main()
registry -> [<function f1 at 0x100631bf8>, <function f2 at 0x100631c80>] # f1,f2실행전부터 존재
running f1()
running f2()
running f3()
3. Decorator-Enhanced Strategy Pattern
promos = []
# decorator로써 사용할 함수
def promotion(promo_func):
promos.append(promo_func)
return promo_func
decorator가 적용되는 함수들
- promos라는 list에 담길 뿐, 적용되는 함수가 그대로 반환됨!
@promotion
def fidelity(order):
"""5% discount for customers with 1000 or more fidelity points"""
return order.total() * .05 if order.customer.fidelity >= 1000 else 0
@promotion
def bulk_item(order):
"""10% discount for each LineItem with 20 or more units"""
discount = 0
for item in order.cart:
if item.quantity >= 20:
discount += item.total() * .1
return discount
@promotion
def large_order(order):
"""7% discount for orders with 10 or more distinct items"""
distinct_items = {item.product for item in order.cart}
if len(distinct_items) >= 10:
return order.total() * .07
return 0
def best_promo(order):
"""Select best discount available"""
return max(promo(order) for promo in promos)
4. Variable Scope Rules
2개의 variables
- 1) local variable (a)
- 2) not defined anywhere (b)
b가 정의되지 않아서 error가 뜬다
>>> def f1(a):
print(a)
print(b)
#----------------------------------------------------------#
>>> f1(3)
3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in f1
NameError: global name 'b' is not defined
#----------------------------------------------------------#
b=2
>>> f1(3)
3
2
함수 “안”에서 b가 정의되었기 때문에, b는 LOCAL이다!
하지만, print문 “이후”에 b가 정의되었기 때문에 error다 뜬다.
b = 6
def f2(a):
print(a)
print(b)
b = 100
f2(3)
#----------------------------------------------------------#
UnboundLocalError: local variable 'b' referenced before assignment
이를 해결하기 위해, 즉 LOCAL이 아닌 “GLOBAL”로 b를 설정하고 싶다면,
global b
를 선언해준다.
b = 6
def f2(a):
global b
print(a)
print(b)
b = 100
f2(3)
print('b는 ',b)
#----------------------------------------------------------#
3
6
b는 100
5. closure
closure : function that retains the bindings of free variables
( so that they can be used later )
def make_averager():
series = []
def averager(new_value):
series.append(new_value)
total = sum(series)
return total/len(series)
return averager
series는 계속 유지된다 ( 초기화 X )
>>> avg = make_averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0
다른 방법 : history keeping안하고도! ( nonlocal
사용하기! )
def make_averager():
count = 0
total = 0
def averager(new_value):
nonlocal count, total
count += 1
total += new_value
return total / count
return averager
>>> avg = make_averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0
6. Stacked Decorators
2개의 decorator @d1
& @d2
가 있으면,
f=d1(d2(f))
꼴이다!