[Python] 데코레이터 제대로 알고 사용하기
Python

[Python] 데코레이터 제대로 알고 사용하기

728x90

파이썬은 데코레이터(decorator)라는 기능을 제공한다. 데코레이터는 장식하다, 꾸미다라는 뜻의 decorate에 er(or)을 붙인 말인데 장식하는 도구 정도로 설명할 수 있다.

 

파이썬에서 데코레이터는 함수를 받아 명령을 추가한 뒤 이를 다시 함수의 형태로 반환하는 함수이다.

함수의 내부를 수정하지 않고 기능에 변화를 주고 싶을 때 사용한다. 

데코레이터를 이용해, 반복을 줄이고 메소드나 함수의 책임을 확장한다.

 

자바, 파이썬 객체지향형 언어의 클래스에서 메서드를 만들 때 @staticmethod, @classmethod, @abstractmethod 등을 붙였는데, 이렇게 @로 시작하는 것들이 데코레이터이다.

 

@데코레이터 사용하기

데코레이터 사용을 위한 예제코드

def Hello():
	print('-' * 15)    
	print('hello 출력시작')
	print('hello')
	print('hello 출력끝')
	print('-' * 15)

def Python():
	print('-' * 15)
	print('python 출력시작')
	print('python')
	print('python 출력끝')
	print('-' * 15)
    
Hello()
Python()

실행결과

-----------
hello 출력시작
hello
hello 출력끝
-----------
-----------
python 출력시작
python
python 출력끝
-----------

 

위 내용처럼 print를 코드실행상 상관없지만 함수가 많아지고 코드가 복잡해지면 매우 번거로운 작업이 될 것이다.

그래서 다음과 같이 호출할 함수 위에 @데코레이터 형식으로 지정하는 방법을 추천한다.

def decorator_test(func):                       # 호출할 함수를 매개변수로 받음
    def print_test():
        print('-' * 15)
        print(func.__name__, '출력시작')        # __name__으로 함수 이름 출력
        func()                                  # 매개변수로 받은 함수를 호출
        print(func.__name__, '출력끝')
        print('-' * 15)
    return print_test                           # print_test 함수 반환
 
@decorator_test    # @데코레이터
def hello():
    print('hello')
 
@decorator_test    # @데코레이터
def Python():
    print('Python')
 
hello()    # 함수를 그대로 호출
Python()    # 함수를 그대로 호출

실행결과

-----------
hello 출력시작
hello
hello 출력끝
-----------
-----------
python 출력시작
python
python 출력끝
-----------

 

지금은 코드길이가 많이 줄지 않았지만 프로젝트 진행하면서 데코레이터를 잘 사용하면 코드 가독성과 깔끔한 코드를 작성하는데 큰 도움이 될 것이다.

 

자주쓰는 데코레이터 알아보기

  • @property
  • @abstractmethod
  • @staticmethod
  • @calssmethod

1. @app.route

route() 메소드는 파이썬 Flask에서 제공하는 데코레이터이다.

route는 외부 웹브라우져에서 웹서버로 접근 시 해당 주소로 입력을 하게 되면 특정 함수가 실행되게 도와주는 기능을 한다.

<예시>

from flask import *

app = Flask(__name__, template_folder="templates")

@app.route('/hello')
def hello():
    return 'hello, world'

if __name__ == "__main__":
    app.run(host='0.0.0.0', debug=True, port=9999)

---실행---

실행결과

2. @property

property 메서드는 python에 내장되어있는 함수이다.

property()를 사용하면 마치 필드명을 사용하는 것처럼 깔끔하게 getter/setter 메서드가 호출되게 할 수 있다.

class Person :
    def __init__(self):
        self.__name = 'kwon'
    def get_name(self): 
        return self.__name
    def set_name(self, name):
        self.__name = name

위와 같이 하나만 있을때는 크게 상관없지만 많은 get, set 메소드를 만들면 함수가 너무 많아지고 복잡해진다.

위 내용을 @property를 통해 좀 더 직관적이고 간단하게 표현할 수 있다.

class Person :
    def __init__(self):
        self.__name = 'kwon'
    @property
    def name(self): 
        return self.__name

    @name.setter
    def name(self, name):
        self.__name = name
        
person = Person()
print(person.name)    # kwon
person.name = 'zeus'
print(person.name)    # zeus

get 역할은 @property

set 역할은 @name.setter가 한다.

주의할 점은 @property가 setter보다 윗줄에 사용되어야 한다.

3. @abstractmethod

@abstractmethod는 인터페이스나 추상클래스에 있는 추상메소드이다.

  • 추상클래스 : 단어 뜻 그대로 완성되지 못한 채로 남겨진 미완성 설계도로 비유할 수 있다.
  • 추상 메서드 : 메서드는 선언부와 구현부로 구성되어있는데 추상메서드는 선언부만 작성하고 구현부는 작성하지 않은 채로 남겨 둔 메서드를 뜻한다.

추상클래스를 사용하는 이유로는 자손 클래스에서 추상메서드를 반드시 구현하도록 강요하기 위해서이다.

상속받는 자손클래스에서는 메서드들이 완전히 구현된 것으로 인식하고 오버라이딩을 하지 않을 수 있기 때문이다.

 

- 문법

Drow 메소드에 abstractmethod 데코레이터를 적용하여 추상 메소드로 정의한다.

from abc import ABC, abstractmethod

class Shape(ABC) :

    @abstractmethod
    def Draw(self) :
        pass

class Rect(Shape) :
    pass

s = Shape() # TypeError: Can't instantiate abstract class Shape with abstract method Draw
r = Rect()
r.Draw()

조상클래스에서 Draw()메서드를 추상메서드로 정의하고 자손클래스 Rect()에서 추상메소드가 구현이 안되어있으면 에러가 발생한다.

from abc import ABC, abstractmethod

class Shape(ABC) :

    @abstractmethod
    def Draw(self) :
        pass

class Rect(Shape) :

    def Draw(self) :
        print('Draw Rect')

r = Rect()  
r.Draw()     # Draw Rect

자손클래스는 추상메서드로 선언된 부분에 대해서 선언해줘야 하는 강제성이 부여 됨

 

4. @staticmethod & @classmethod 정적메소드

@staticmethod와 @classmethod는 정적메소드로 분류가 된다.

  • 정적메소드는 클래스에서 직접 접근할 수 있는 메소드이다.
  • 파이썬에서는 정적메소드임에도 불구하고 인스턴스에서도 접근이 가능하다.
  • 둘 다 인스턴스를 만들지 않아도 class의 메서드를 바로 실행 할 수 있다.

(예시)

- @staticmethod

class Calc:
    
    @staticmethod
    def add(a ,b):
        return a + b

cal = Calc()
cal.add(1,2)  # return 3

- @classmethod

class Calc:
    
    @classmethod
    def add(cls, a ,b):
        return a + b
cal = Calc()
cal.add(1,2)  # return 3

두 클래스의 차이점으로 @classmethod에서 cls라는 인자를 추가로 가진다. 

두 정적 메소드의 차이는 상속을 받을 때 차이가 난다.

@Classmethod와 @staticmethod의 차이

class Person:
    default= "조상"
    
     def __init__(self):
        self.data = self.default
    
    @classmethod
    def class_person(cls):
        return cls()
    
    @staticmethod
    def static_person():
        return Person()
    
class WhatPerson(Person):
    default = "손자"
person1 = WhatPerson.class_person()    # return 손자
person2 = WhatPerson.static_person()   # return 조상

@staticmethod의 경우 부모 클래스의 클래스 속성 값을 가져오지만 @classmethod의 경우 cls인자를 활용하여 현재 클래스의 클래스 속성을 가져온다.

728x90