파이썬 문법 중 하나인 데코레이터(Decorator)는 wrapping 방식을 통해 함수를 유연하게 적용하여, 특정 기능을 편리하게 사용하게 해줍니다.
아래처럼 특수기호 @를 붙여서 사용하는 방식이 데코레이터입니다.
@tf.function
def train_step(args):
pass
글에서는 데코레이터의 개념, 동작방식이 아닌 몇 가지 유용하게 사용되는 또는 사용될만한 데코레이터를 알아보도록 하겠습니다.
@retry
@retry 데코레이터는 특정 예외가 발생한 경우, 설정한 횟수만큼 재시도하는 데코레이터입니다.
일반적인 상황에서는 잘 사용되진 않지만, 네트워크, DB 통신에서 유용하게 사용되는 함수입니다.
retrying 라이브러리나 직접 구현하여 사용하는 방법이 있지만, tenacity 라이브러리가 유명하게 사용됩니다. 아래 코드는 tenacity 공식 문서에 나와있는 예제입니다.
- tenacity 공식 문서: https://tenacity.readthedocs.io/en/latest/
import random
from tenacity import retry
@retry
def do_something_unreliable():
if random.randint(0, 10) > 1:
raise IOError("Broken sauce, everything is hosed!!!111one")
else:
return "Awesome sauce!"
print(do_something_unreliable())
성공할때까지의 시도, wait 횟수 등도 parameter를 통해 조절할 수 있게 제공해줍니다.
@classmethod
@classmethod는 아래 @staticmethod와 함께 보면 좋을 것 같습니다.
어느정도 자바/C 언어를 경험한 분들이라면 클래스 또는 정적 변수에 익숙할 것입니다. 그 개념 그대로 파이썬에서도 클래스 변수, 클래스 메소드를 사용할 수 있습니다.
단일 클래스나 상속받은 클래스의 변수를 조정할 때 유용하게 사용됩니다.
암묵적으로 @classmethod 데코레이터를 사용하면 첫 번째 인자로 cls 파라미터를 사용합니다. 인스턴스 메소드라 불리우는 self와 비슷한 개념입니다. 우리는 cls 파라미터를 통해 클래스 변수에 접근할 수 있게 됩니다.
위에서 언급하였듯이 @classmethod를 통해 클래스 변수를 조정할 수 있고, upgrade_os 메소드처럼 새로운 생성자를 만들어 줄 수도 있습니다.
class Computer:
os = 'Linux' # 클래스 변수
class Personal_Computer(Computer):
def __init__(self, c_id, pos):
self.c_id = c_id
self.partial_os = pos
@classmethod
def change_os(cls, this_os):
if cls.os != this_os:
cls.os = this_os
else:
print(f'{cls.os} Already up-to-date!')
@classmethod
def upgrade_os(cls, c_id, new_os):
return cls(c_id, new_os)
computer_1 = Personal_Computer('2021', 'window11')
computer_2 = Personal_Computer('2020', 'window10')
# 변경 전, Linux -> Window
print(f'os change Before: {computer_1.os}, {computer_2.os}')
Personal_Computer.change_os(this_os = 'Window')
# 변경 후
print(f'os change After: {computer_1.os}, {computer_2.os}', end = '\n\n')
# upgrade os
print(f'os upgrade Before: {computer_2.partial_os}')
upgraded_computer_2 = computer_2.upgrade_os(computer_1.c_id, 'window11')
print(f'os change After: {upgraded_computer_2.partial_os}')
@staticmethod
정적 메소드입니다. 이 데코레이터를 사용하면 아래 코드처럼 객체 생성없이 바로 클래스를 통해 접근하여 사용이 가능합니다. 유용해보이지만, 많이 사용하지 않는 기능입니다.
class Computer:
os = 'Linux' # 클래스 변수
class Personal_Computer(Computer):
..생략..
@staticmethod
def print_os():
print(Computer.os)
Personal_Computer.print_os() # Linux
@property
@property 데코레이터는 getter/setter를 떠올리면 쉽다. 아래 코드처럼 사용 가능합니다.
@property는 getter, @name.setter는 setter 역할을 합니다.
class ERP:
def __init__(self):
self._salary = 100
@property
def salary(self):
return self._salary
@salary.setter
def salary(self, value):
if self._salary < 500:
self._salary = value
else:
raise ValueError('No!')
erp = ERP()
print(erp.salary) # 100
erp.salry = 200
print(erp.salry) # 200
물론 파이썬 특성상 이렇게 하지않아도 변수에 자유롭게 접근하거나 새로운 값을 할당할 수 있습니다.
다만 파이썬에서는 언더바('_')를 활용해서 private/protected 의미를 부여하는데요.
'_name' 은 private, '__name'은 protected를 암묵적으로 의미합니다.
언더바를 사용하면 우리가 흔히 사용하는 기능인 객체 메소드 참조(예를 들면 Tab키를 눌러서 어떤 함수가 확인하는)가 되지 않습니다. 참조하려면 언더바를 명시적으로 붙여주어야 합니다.
뿐만 아니라 @name.setter의 기능은 당연히 setter와 같지만, 위의 예제처럼 변수 할당에 조건을 편리하게 걸 수 있다는 장점이 있습니다.
@dataclass
Python 3.7이상 버전부터 사용할 수 있는 @dataclass 데코레이터는 __init__, __eq__, __repr__ 등 함수를 자동으로 등록해주는 기능입니다.
from dataclasses import dataclass
@dataclass
class Person:
name: str
age: int
person1 = Person('kim', 10)
person2 = Person('kim', 10)
print(person1.name, person1.age)
print(person1 == person2)
위처럼 별도의 함수를 정의해주지 않아도 바로 사용할 수 있습니다.
@lru_cache
@lru_cache는 메모라이제이션(memorization) 기능을 의미합니다.
이 데코레이터를 사용하면 함수의 반환값을 max_size 크기까지 값을 저장할 수 있습니다.
from functools import lru_cache
import urllib
@lru_cache(maxsize = 32)
def get_pep(num):
'Retrieve text of a Python Enhancement Proposal'
resource = 'https://www.python.org/dev/peps/pep-%04d/' % num
try:
with urllib.request.urlopen(resource) as s:
return s.read()
except urllib.error.HTTPError:
return 'Not Found'
id_check = []
for i in [8, 290, 308, 320, 8, 218, 320, 279, 289, 320, 9991]:
pep = get_pep(i)
if i == 8:
id_check.append(pep)
print(get_pep.cache_info()) # 총 hits 3 -> 320 2번, 8 1번 hit
assert id_check[0] is id_check[1] # id 비교, @lru_cache 사용안할 시 에러 발생!
@lru_cache 기능으로 인해 id_check에 들어가는 객체 id가 동일해서(캐시를 이용하기 때문) AssertionError가 발생하지 않습니다