ch07. decorator

|

Decorator

Decorator의 작동 방식

@decorate
def target():
    print('running target()')

은 다음처럼 동작한다.

def target():
    print('running target()')
  
target = decorate(target)
  • decorate된 함수가 정의된 직후에 실행됨

변수 범위 규칙

  • 함수 scope 내부에 정의되어있지 않은 변수의 경우 global 변수를 이용
  • 지역변수를 함수 내부에 만들면 global 변수를 참조하지 않게됨. 따라서 다음과 같은 코드에서 문제가 생김
b = 6
def f2(a):
    print(a)
    print(b)
    b = 9

>>> f2(1)
1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in f2
UnboundLocalError: local variable 'b' referenced before assignment
  • 이 때는 global b를 추가해서 b가 전역변수라는 것을 알려주면 됨

Closure

  • 함수 본체에서 정의하지 않고 참조하는 비전역(nonlocal) 변수를 포함한 확장 범위를 가진 함수
  • free variable을 얻으려면 fn.__code__.co_freevars를 사용
  • 다음처럼 만들면 에러가 남!
def make_averager():
    count = 0
    total = 0
    
    def averager(new_value):
        count += 1
        total += new_value
        return total / count
    return averager
>>> avg = make_averager()
>>> avg(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in averager
UnboundLocalError: local variable 'count' referenced before assignment
  • count += 1 구문에서 count를 local 변수로 생각했기 때문이다.
  • 앞의 global처럼 nonlocal count, total을 붙여주면 해결!

유용한 decorator들

functools.lru_cache()

  • decorate할 함수가 받는 인수는 모두 hashable해야함
    • dictionary로 결과를 저장하기 때문에…
  • 밑의 코드를 돌려보자!
import functools

def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-2) + fibonacci(n-1)

@functools.lru_cache(maxsize=128, typed=False)
def fibonacci_cache(n):
    if n < 2:
        return n
    return fibonacci_cache(n-2) + fibonacci_cache(n-1)

functools.singledispatch()

  • method overloading과 비슷함
import functools

@functools.singledispatch
def hello(obj):
    raise NotImplemented

@hello.register(int)
def _(i):
    print("hello integer! args: {}".format(i))

@hello.register(str)
def _(s):
    print("hello string! args: {}".format(s))

매개변수화된 Decorator flask의 route decorator

  • flask의 app.route를 대신해서 보자!
def route(self, rule, **options):
    def decorator(f):
        endpoint = options.pop('endpoint', None)
        self.add_url_rule(rule, endpoint, f, **options)
        return f
   return decorator

@app.route('/')
def index_fn():
    return 'hello world'
  • app.route('/')(index_fn) => decorator(index_fn)