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))꼴이다!

Tags:

Categories:

Updated: