매일 업데이트
2023-05-23 12:05 13 min

설명됨(예제 및 사용 사례 포함)

파이썬 데코레이터는 파이썬 프로그래밍에서 매우 강력하고 유용한 기능입니다. 데코레이터를 활용하면 기존 함수를 감싸서 그 동작 방식을 변경하거나 확장할 수 있습니다. 이는 코드의 가독성을 높이고, 기능 재사용성을 향상시키는 데 도움을 줍니다. 이 글에서는 파이썬 데코레이터의 개념을 소개하고, 데코레이터를 직접 만드는 방법과 다양한 활용 예시를 상세히 다룹니다.

필수 선행 지식

파이썬 데코레이터를 효과적으로 이해하기 위해서는 몇 가지 기본 지식이 필요합니다. 다음은 이 튜토리얼을 학습하기 전에 숙지해야 할 핵심 개념들입니다. 필요하다면 아래 링크된 자료를 통해 관련 내용을 복습하는 것을 추천합니다.

파이썬 기초

데코레이터는 중급 또는 고급 주제에 해당하므로, 파이썬의 기본적인 문법과 개념에 익숙해야 합니다. 예를 들어, 데이터 타입, 함수, 객체, 클래스 등에 대한 이해가 필수적입니다. 또한, 게터(getter), 세터(setter), 생성자(constructor)와 같은 객체 지향 프로그래밍(OOP) 개념에 대한 이해도 필요합니다. 파이썬 프로그래밍 경험이 부족하다면, 입문자용 자료를 먼저 학습하는 것이 좋습니다.

함수는 일급 객체

파이썬에서는 함수를 일급 객체로 취급합니다. 이는 함수가 숫자나 문자열과 같이 다른 객체와 동일하게 다뤄질 수 있다는 것을 의미합니다. 구체적으로 함수는 다음과 같은 작업을 수행할 수 있습니다.

  • 다른 함수의 인수로 전달될 수 있습니다.
  • 다른 함수에 의해 반환될 수 있습니다.
  • 변수에 저장될 수 있습니다.

함수가 다른 객체와 다른 점은 __call__()이라는 매직 메서드를 가지고 있다는 것입니다. 이 메서드를 통해 함수를 호출할 수 있습니다. 이제 필요한 사전 지식을 모두 갖추었으므로, 본격적으로 데코레이터에 대해 알아보겠습니다.

파이썬 데코레이터란 무엇인가?

파이썬 데코레이터는 함수를 인자로 받아서, 해당 함수의 기능을 확장하거나 변경한 새로운 함수를 반환하는 함수입니다. 즉, 함수 '데코레이터'는 다른 함수 '원본함수'를 인수로 받아, '변형함수'를 반환하는 형태입니다. '변형함수'는 '원본함수'를 내부적으로 호출하며, 호출 전후에 추가적인 동작을 수행할 수 있습니다. 좀 더 쉽게 설명하기 위해 아래 코드 예시를 보겠습니다.

    # 'foo'는 데코레이터 함수로, 다른 함수 'bar'를 인자로 받습니다.
    def foo(bar):

        # 'baz'는 'bar'의 수정된 버전으로, 'bar'를 호출 전후에 추가 작업을 수행합니다.
        def baz():

            # 'bar' 호출 전에 특정 동작을 수행합니다.
            print("무언가 실행 전")

            # 'bar' 함수를 호출합니다.
            bar()

            # 'bar' 호출 후에 다른 동작을 수행합니다.
            print("무언가 실행 후")

        # 'bar'를 수정하여 생성한 'baz' 함수를 반환합니다.
        return baz

파이썬 데코레이터 생성 방법

파이썬에서 데코레이터를 어떻게 만들고 사용하는지 실제 예시를 통해 설명하겠습니다. 아래 예시는 함수가 실행될 때마다 해당 함수의 이름을 기록하는 로거(logger) 데코레이터 함수를 생성하는 과정입니다.

먼저, 데코레이터 함수를 정의합니다. 데코레이터는 인자로 함수 `func`를 받는데, 이 `func`가 우리가 데코레이팅할 함수입니다.

    def create_logger(func):
        # 데코레이터 함수 내부 코드는 여기에 작성됩니다.

다음으로, 데코레이터 함수 내부에서 `func`를 실행하기 전에 함수의 이름을 기록하는 수정된 함수를 만듭니다.

    # create_logger 함수 내부
    def modified_func():
        print("함수 호출: ", func.__name__)
        func()

마지막으로, `create_logger` 함수는 수정된 함수를 반환합니다. 최종적인 `create_logger` 함수는 다음과 같습니다.

    def create_logger(func):
        def modified_func():
            print("함수 호출: ", func.__name__)
            func()

        return modified_func

이렇게 데코레이터 생성이 완료되었습니다. `create_logger` 함수는 데코레이터의 기본적인 형태를 보여줍니다. 데코레이팅할 함수 `func`를 입력받아, `func`의 이름을 출력한 후 `func`를 실행하는 `modified_func`를 반환합니다.

파이썬 데코레이터 사용법

데코레이터를 함수에 적용하려면 `@` 기호를 사용합니다. 아래 코드는 `say_hello` 함수에 `create_logger` 데코레이터를 적용하는 예시입니다.

    @create_logger
    def say_hello():
        print("안녕하세요!")

이제 `say_hello()`를 호출하면 다음과 같은 출력이 나타납니다.

    함수 호출:  say_hello
    안녕하세요!

`@create_logger`는 어떻게 동작하는 걸까요? 이는 `say_hello` 함수에 `create_logger` 데코레이터를 적용하는 것입니다. 다음 코드는 `@create_logger`를 사용하는 것과 같은 효과를 냅니다.

    def say_hello():
        print("안녕하세요!")

    say_hello = create_logger(say_hello)

데코레이터를 사용하는 두 가지 방법이 있습니다. 명시적으로 데코레이터 함수를 호출하여 함수를 인자로 넘기는 방법과 `@` 기호를 사용하는 방법입니다. `@` 기호는 더 간결하게 데코레이터를 적용할 수 있게 해줍니다.

이제 파이썬 데코레이터를 만드는 방법과 사용하는 방법에 대한 기본 개념을 이해했을 것입니다.

약간 더 복잡한 예시

위의 예시는 간단한 경우였고, 이제 함수가 인자를 받는 경우나 클래스를 데코레이팅하는 경우와 같이 좀 더 복잡한 예시를 살펴보겠습니다.

함수가 인자를 받는 경우

데코레이팅할 함수가 인자를 받는 경우, 수정된 함수는 이 인자를 받아서 원본 함수를 호출할 때 전달해야 합니다. 좀 더 구체적으로, 데코레이터 함수가 `foo`, 데코레이팅할 함수가 `bar`, 데코레이팅된 함수가 `baz`라고 할 때, `bar`가 인자를 받으면 `baz`는 인자를 받고, 그 인자를 `bar`에게 전달해야 합니다. 아래 코드를 통해 개념을 명확히 할 수 있습니다.

    def foo(bar):
        def baz(*args, **kwargs):
            # 여기에 추가 작업을 수행할 수 있습니다.
            ___
            # 'bar' 함수를 인자와 함께 호출합니다.
            bar(*args, **kwargs)
            # 여기에도 추가 작업을 수행할 수 있습니다.
            ___

        return baz

`*args`와 `**kwargs`는 각각 위치 인자와 키워드 인수를 받는 데 사용됩니다. `baz` 함수는 인자에 접근할 수 있으므로 `bar` 함수를 호출하기 전에 인자의 유효성을 검사하는 작업을 수행할 수 있습니다.

예를 들어, 데코레이팅하는 함수에 전달된 인자가 문자열인지 확인하는 `ensure_string` 데코레이터를 만들어 보겠습니다.

    def ensure_string(func):
        def decorated_func(text):
            if type(text) is not str:
                 raise TypeError('인자는 문자열이어야 합니다: ' + func.__name__)
            else:
                 func(text)

        return decorated_func

`say_hello` 함수를 다음과 같이 데코레이팅할 수 있습니다.

    @ensure_string
    def say_hello(name):
        print('안녕하세요', name)

다음과 같이 코드를 테스트해 볼 수 있습니다.

    say_hello('홍길동') # 문제 없이 실행됩니다.
    say_hello(123) # 예외가 발생합니다.

위 코드는 다음과 같은 출력을 생성합니다.

    안녕하세요 홍길동
    Traceback (most recent call last):
    File "...", line ..., in 
    say_hello(123) # 예외가 발생합니다.
    File "...", line ..., in decorated_func
    raise TypeError('인자는 문자열이어야 합니다: ' + func.__name__)
    TypeError: 인자는 문자열이어야 합니다: say_hello

`'홍길동'`은 문자열이므로 정상적으로 실행되어 `'안녕하세요 홍길동'`을 출력합니다. `123`은 문자열이 아니므로 예외를 발생시킵니다. `ensure_string` 데코레이터는 문자열 인수를 필요로 하는 모든 함수에 적용할 수 있습니다.

클래스 데코레이팅

함수뿐만 아니라 클래스도 데코레이팅할 수 있습니다. 클래스에 데코레이터를 추가하면, 데코레이팅된 메서드가 클래스의 생성자(`__init__`)를 대체하게 됩니다. 데코레이터 함수가 `foo`, 데코레이팅할 클래스가 `Bar`라고 할 때, `foo`는 `Bar.__init__`을 데코레이팅합니다. 이는 클래스가 인스턴스화되기 전에 특정 작업을 수행하고자 할 때 유용합니다.

다음 코드는

    def foo(func):
        def new_func(*args, **kwargs):
            print('객체 생성 전에 수행됩니다.')
            func(*args, **kwargs)

        return new_func

    @foo
    class Bar:
        def __init__(self):
            print("생성자 내부")

다음 코드와 동일한 결과를 보여줍니다.

    def foo(func):
        def new_func(*args, **kwargs):
            print('객체 생성 전에 수행됩니다.')
            func(*args, **kwargs)

        return new_func

    class Bar:
        def __init__(self):
            print("생성자 내부")

    Bar.__init__ = foo(Bar.__init__)

실제로 두 방법 중 어떤 것을 사용하여 `Bar` 클래스의 객체를 생성하든 동일한 출력이 나타납니다.

    객체 생성 전에 수행됩니다.
    생성자 내부

파이썬 내장 데코레이터 예시

파이썬에는 내장된 데코레이터가 많이 있습니다. 다음은 파이썬에서 흔히 볼 수 있는 몇 가지 데코레이터입니다.

`@staticmethod`

정적 메서드 데코레이터는 클래스 내 메서드가 정적 메서드임을 나타낼 때 사용됩니다. 정적 메서드는 클래스 인스턴스를 생성하지 않고도 호출할 수 있는 메서드입니다. 다음 코드 예시에서 `bark` 메서드를 정적 메서드로 정의한 `Dog` 클래스를 살펴보겠습니다.

    class Dog:
        @staticmethod
        def bark():
            print('멍멍!')

이제 `bark` 메서드는 다음과 같이 클래스 이름으로 직접 호출할 수 있습니다.

    Dog.bark()

위 코드를 실행하면 다음과 같은 출력이 생성됩니다.

    멍멍!

데코레이터 사용법 섹션에서 언급했듯이, 데코레이터는 두 가지 방법으로 사용할 수 있습니다. `@` 기호를 사용하는 방법은 더 간결하고 가독성이 높습니다. 아래 코드는 위 코드와 동일한 결과를 생성합니다.

    class Dog:
        def bark():
            print('멍멍!')

    Dog.bark = staticmethod(Dog.bark)

여전히 동일한 방법으로 `bark` 메서드를 사용할 수 있습니다.

    Dog.bark()

위 코드는 다음과 같은 출력을 생성합니다.

    멍멍!

보시다시피, 첫 번째 방법이 더 깔끔하고 해당 메서드가 정적 메서드임을 명확하게 보여줍니다. 따라서, 앞으로의 예시에서는 첫 번째 방법을 사용하겠습니다. 하지만 두 번째 방법도 대안이 될 수 있다는 것을 기억해야 합니다.

`@classmethod`

이 데코레이터는 클래스 내 메서드가 클래스 메서드임을 나타낼 때 사용됩니다. 클래스 메서드는 클래스 인스턴스 없이 호출할 수 있다는 점에서 정적 메서드와 유사합니다. 하지만 클래스 메서드는 클래스 속성에 접근할 수 있지만, 정적 메서드는 그렇지 않습니다. 이는 파이썬이 클래스 메서드를 호출할 때 클래스를 인자로 자동으로 전달하기 때문입니다. 다음은 클래스 메서드를 만드는 데 `classmethod` 데코레이터를 사용하는 예입니다.

    class Dog:
        @classmethod
        def what_are_you(cls):
            print("나는 " + cls.__name__ + "이야!")

위 코드를 실행하려면 클래스를 인스턴스화하지 않고 다음과 같이 메서드를 호출하면 됩니다.

    Dog.what_are_you()

결과는 다음과 같습니다.

    나는 Dog이야!

`@property`

`property` 데코레이터는 메서드를 속성처럼 접근 가능하게 만들어 줍니다. `Dog` 예시로 돌아가서, 강아지의 이름을 반환하는 메서드를 만들어 보겠습니다.

    class Dog:
        # 강아지 이름을 인자로 받는 생성자
        def __init__(self, name):

            # 비공개 속성으로 이름 저장
            # 언더스코어 두 개는 속성을 비공개로 만듭니다.
            self.__name = name

        @property
        def name(self):
            return self.__name

이제 다음과 같이 일반 속성처럼 강아지의 이름을 가져올 수 있습니다.

    # 클래스 인스턴스 생성
    foo = Dog('멍멍이')

    # 이름 속성에 접근
    print("강아지 이름은:", foo.name)

위 코드를 실행하면 다음과 같은 결과를 얻을 수 있습니다.

    강아지 이름은: 멍멍이

`@property.setter`

`property.setter` 데코레이터는 속성에 대한 세터(setter) 메서드를 만드는 데 사용됩니다. `@property.setter` 데코레이터를 사용하려면, `property`를 속성 이름으로 대체하고 세터 메서드를 정의합니다. 예를 들어, `foo` 속성에 대한 세터를 만들려면 데코레이터는 `@foo.setter`가 됩니다. `Dog` 예시로 설명하겠습니다.

    class Dog:
        # 강아지 이름을 인자로 받는 생성자
        def __init__(self, name):

            # 비공개 속성으로 이름 저장
            # 언더스코어 두 개는 속성을 비공개로 만듭니다.
            self.__name = name

        @property
        def name(self):
            return self.__name

        # 이름 속성에 대한 세터 생성
        @name.setter
        def name(self, new_name):
            self.__name = new_name

다음 코드를 사용하여 세터를 테스트할 수 있습니다.

    # 새로운 강아지 객체 생성
    foo = Dog('멍멍이')

    # 강아지 이름 변경
    foo.name="야옹이"

    # 강아지 이름 출력
    print("강아지 새 이름은:", foo.name)

위 코드를 실행하면 다음과 같은 출력이 생성됩니다.

    강아지 새 이름은: 야옹이

파이썬 데코레이터의 중요성

데코레이터가 무엇인지, 몇 가지 예시를 살펴봤으니 이제 데코레이터가 파이썬에서 왜 중요한지에 대해 논의할 수 있습니다. 데코레이터는 여러 가지 이유로 중요합니다. 몇 가지 이유를 아래에 제시합니다.

  • 코드 재사용이 용이합니다. 위에서 제시된 로깅 예시에서 `@create_logger` 데코레이터를 원하는 모든 함수에 적용할 수 있습니다. 이를 통해 모든 함수에 로깅 기능을 직접 작성하지 않고도 쉽게 추가할 수 있습니다.
  • 모듈화된 코드를 작성할 수 있습니다. 다시 로깅 예시를 보면, 데코레이터를 사용하여 핵심 기능(이 경우 `say_hello`)과 추가 기능(이 경우 로깅)을 분리할 수 있습니다.
  • 프레임워크와 라이브러리 개선에 기여합니다. 데코레이터는 추가 기능을 제공하기 위해 파이썬 프레임워크와 라이브러리에서 광범위하게 사용됩니다. 예를 들어, Flask 또는 Django와 같은 웹 프레임워크에서 데코레이터는 경로를 정의하거나, 인증을 처리하거나, 특정 뷰에 미들웨어를 적용하는 데 사용됩니다.

마지막으로

데코레이터는 매우 유용합니다. 함수 기능을 변경하지 않고 기능을 확장하는 데 사용할 수 있습니다. 함수 실행 시간을 측정하거나, 함수 호출을 기록하거나, 함수 호출 전에 인수를 확인하거나, 함수를 실행하기 전에 권한을 확인해야 하는 경우 유용합니다. 데코레이터를 이해하면 더 깨끗한 방식으로 코드를 작성할 수 있습니다.

다음으로는 튜플과 파이썬에서 cURL 사용하는 방법에 대한 기사를 읽어보시는 것을 추천합니다.

저자
Korea

기술 트렌드와 실용적인 팁을 전하는 लेखक입니다.