반응형

Python 의 decorator 는 funciton 이나 class에 코드의 내용을 변경하지 않고도 기능을 확장할 수 있는 아주 유용한 툴입니다. 

Decorator를 이해하기 위해서는 몇 가지 알아두면 좋은 내용들이 있어서 먼저 알아보고, decorator에 대해서 알아보도록 하겠습니다. 

 

 

First Class Objects

먼저 한국말로는 일급 객체라고 이야기하는 first class objects에 대해서 알아보겠습니다. 일급 객체는 다른 객체들에 일반적으로 적용 가능한 연산을 모두 지원하는 객체를 가리킵니다. 보통 함수에 인자로 넘기기, 수정하기, 변수에 대입하기와 같은 연산을 지원할 때 일급 객체라고 합니다.

 

일급 객체는 다음과 같은 특징이 있습니다. (by 로빈 포플스톤)

  • 모든 요소는 함수의 실제 매개변수가 될 수 있다.
  • 모든 요소는 함수의 반환 값이 될 수 있다.
  • 모든 요소는 할당 명령문의 대상이 될 수 있다.
  • 모든 요소는 동일 비교의 대상이 될 수 있다.

 

Python에서는 function 도 일급 객체로 분류됩니다. 아래 예시를 보면 쉽게 이해가 될 겁니다.

def add_10(val):
    return val + 10

def run_func(f, val):
    return f(val)

# (add_10 함수를) 매개변수로 사용
run_func(add_10, 10)

# (add_10 함수를) 반환값으로 사용
def plus_10():
    return add_10

# (add_10 함수를) 다른 변수에 할당
plus_ten = add_10

# 비교의 대상으로 사용
print(add_10 is run_func)

 

여기서 일급객체를 특별히 설명한 이유는 Python에서는 "function"이 일급객체이기 때문에, 매개변수로 사용할 수 있으며, function을 리턴 값으로 사용할 수 있다는 특징을 상기시키기 위함입니다. 많은 프로그래밍 언어들이 function을 일급객체로 사용하기 때문에 특별히 거부감이 있는 컨셉은 아닐겁니다.

 

 

Inner Functions

Python에서는 함수의 내부에 다른 함수를 정의하는 것이 가능합니다. 내부에 정의된 함수를 inner functions 라고 부르는 데요.. 한국말로는 내부 함수가 적절할 표현일 것 같습니다. (어떤 분은 내장 함수라고 번역하시기도 했는데요, 일반적으로 내장함수는 프로그래밍 언어에서 지원해주는 기본 함수를 이야기 하기 때문에 내부 함수라는 표현이 더 적절할 것 같습니다.)

 

아래 예시를 보면, parent 함수 내에 child  함수를 정의해서 사용하였습니다. child 함수의 scope은 parent 함수 내에서만 유효합니다.

def parent():
    print("parent")
    def child():
    	print("child")
    child()

 

 

바로 전에서 이야기했던 일급객체의 개념을 같이 사용하면 아래와 같은 사용이 가능합니다.

def parent(num):
    def first_child():
        return "I'm first child"
    def second_child():
        return "I'm second child"
    def others():
        return "not exist"
    
    # match - case는 python 3.10 이후 부터 지원
    match num:
        case 1:
            return first_child
        case 2:
            return second_child
        case _:
            return others

child1 = parent(1)
print(child1())

child2 = parent(2)
print(child2())

child3 = parent(3)
print(child3())

 

 

Simple Decorators

위에서 decorator를 구현하기 위한 중요한 컨셉들에 대해서 알아보았으니, 이제 decorator로 넘어갈 차례입니다. 먼저 아래 예시를 살펴보겠습니다.

 

def decorator(func):
    def wrapper():
        print("BEFORE")
        func()
        print("AFTER")
    return wrapper

def some_function():
    print("RUN")

some_function = decorator(some_function)

 

위의 예시에는 decorator() 함수와, some_function() 함수가 정의되어 있고, decorator() 함수에는 wrapper() 라는 내부 함수가 정의되어 있습니다. 그리고 마지막 줄에서는 some_function()을 decorator() 함수를 실행한 결과로 변경하였습니다.

 

여기서 some_function()을 실행하면 아래와 같은 결과가 화면에 출력될 겁니다.

>>> some_function()
BEFORE
RUN
AFTER

 

 

원래 some_function() 함수에서는 "RUN"만 출력해 주는데, decorator()를 통해서 변경한 some_function()은 "BEFORE", "RUN", "AFTER"를 차례로 출력해 줍니다. 쉽게 이야기해서 decorator() 함수가 some_function() 함수를 감싸서, 변경해 주었다고 할 수 있습니다.

 

실제로 decorator를 사용할 때는, 아래와 같이 @ 기호를 붙여서 함수 앞에 붙여 주면 됩니다. 아래 예시에서는 @decorator 는 some_function = decorator(some_function) 를 짧게 표현해 주는 방법으로 생각할 수 있습니다.

def decorator(func):
    def wrapper():
        print("BEFORE")
        func()
        print("AFTER")
    return wrapper

@decorator
def some_function():
    print("RUN")

 

 

아래처럼 check_performance() decorator를 이용하면, 각 함수의 수행 시간을 알 수 있습니다.

import time
def check_performance(func):
    def wrapper():
        begin = time.time()
        func()
        end = time.time()
        print("Time: ", func.__name__, end - begin)
    return wrapper

@check_performance
def some_function():
    print("RUN")

 

 

매개 변수가 있는 함수를 위한 decorator

이전 예시에서는 매개변수가 없는 간단한 함수에 대한 decorator를 정의해서 사용해 봤는데요, 이번에는 원래 함수에 매개변수가 있는 경우에 대해서 decorator를 작성해 보겠습니다.

 

위의 예시를 조금 변경해보면, 다음과 같습니다. *args 와 **kwargs 를 이용해서 매개변수를 전달해 주면 됩니다.

import time
def check_performance(func):
    def wrapper(*args, **kwargs):
        begin = time.time()
        func(*args, **kwargs)
        end = time.time()
        print("Time: ", func.__name__, end - begin)
    return wrapper

@check_performance
def some_function(some_param):
    print("RUN", some_param)

 

 

Decorator 함수에서 결과를 돌려 받기

지금까지의 예시에서는 화면에 결과를 출력하고 끝내는 간단한 예시였는데, 이번에는 결과를 돌려받는 경우를 가정해 보겠습니다.

 

import time
def check_performance(func):
    def wrapper(*args, **kwargs):
        begin = time.time()
        func(*args, **kwargs)
        end = time.time()
        print("Time: ", func.__name__, end - begin)
    return wrapper

@check_performance
def say_hi(name):
    return f"Hi {name}"

 

 

print( say_hi("Alex") ) 를 실행해 보면, 함수의 수행 시간과 "None" 만 출력될 뿐, "Hi Alex" 는 출력되지 않습니다.

>>> print(say_hi("Alex"))
Time:  say_hi 2.1457672119140625e-06
None

 

 

아래와 같이 wrapper를 수정하면, 원하는 결과를 돌려받을 수 있습니다. 

 

import time
def check_performance(func):
    def wrapper(*args, **kwargs):
        begin = time.time()
        ret = func(*args, **kwargs)
        end = time.time()
        print("Time: ", func.__name__, end - begin)
        return ret
    return wrapper

@check_performance
def say_hi(name):
    return f"Hi {name}"

 

 

Decorator 에 parameter 설정하기

이번에는 decorator 에 parameter를 설정하는 방법을 살펴보겠습니다.

 

아래 예시는 Django에 내장되어 있는 login_required decorator를 커스텀 버전으로 구현해 본 코드입니다. (실제 구현은 아래 코드와는 상이하게 작성이 되어 있습니다. 실제 코드가 궁금하신 분은 다음 링크 참고하세요. https://github.com/django/django/blob/main/django/contrib/auth/decorators.py)

from django.http import HttpResponse
from django.http import HttpResponseRedirect

def custom_login_required(*args, **kwargs):
    def _inner_func(func):
        def wrapper(*f_args, **f_kwargs):
            request = f_args[0]
            if request.user.is_authenticated:
                return func(*f_args, **f_kwargs)
            else:
                return HttpResponseRedirect(kwargs.get('login_url', '/') + f"?next={request.path}")
        return wrapper
    return _inner_func

@custom_login_required(login_url='/login/')
def test_view(request):
	return HttpResponse('success')

 

 

참고로 parameter가 필요한 decorator를 사용하게 되면, 실제 전달 인자가 없는 경우에도 함수를 호출하는 형태로 decorator를 사용해야 합니다. 

# Error: 'function' object has no attribute 'get'
@custom_login_required
def test_view_wrong(request):
	return HttpResponse('success')
    
# working!
@custom_login_required()
def test_view_correct(request):
	return HttpResponse('success')

 

 

참조 웹 사이트

이 글을 작성하면서 아래 웹 사이트들을 참조하였습니다. 이 글에서 다루지 않은 내용들도 있으니 한번 확인해 보시는 것도 좋을 듯 합니다.

 

https://realpython.com/primer-on-python-decorators/

 

Primer on Python Decorators – Real Python

In this tutorial, you'll look at what Python decorators are and how you define and use them. Decorators can make your code more readable and reusable. Come take a look at how decorators work under the hood and practice writing your own decorators.

realpython.com


https://www.geeksforgeeks.org/decorators-in-python/

 

Decorators in Python - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

 

반응형

'프로그래밍 > Python' 카테고리의 다른 글

9. 파일 관리  (0) 2019.02.21
8. 파일 읽고 쓰기  (0) 2019.02.21
7. 정규 표현식  (0) 2019.01.09
6. 문자열 다루기  (0) 2019.01.08
5. 사전 (Dictionary)  (0) 2019.01.07

+ Recent posts