합성_가능한_클래스_확장

매일 공부(ML)·2022년 7월 20일
0

이어드림

목록 보기
103/146

합성 가능한 클래스 확장이 필요하면 메타클래스보다는 클래스 데코레이터를 사용하라

메타클래스를 사용하면 클래스 생성을 다양한 방법으로 커스텀화할 수 있지만, 여전히 메타클래스로 처리할 수 없는 경우가 있다.

#전달 인자, 반환 값, 예외 출력

#디버깅 데코레이터 정의

"""
아래의 식을 활용하면, 모든 메서드를 @trace_func 데코레이터를 써서 재정의
불필요한 중복으로 인해서 가독성 저하및 실수가 쉬워짐
"""

from functools import wraps

def trace_func(func):
    if hasattr(func, 'tracing'): #단 한 번만 데코레이터 적용
        return func

    @wraps(func)
    def wrapper(*args, **kwargs):
        result = None
        try:
            result = func(*args, **kwargs)
            return result
        except Exception as e:
            result = e
            raise
        finally:
            print(f'{func.__name__}({args!r}, {kwargs!r}) ->'
                  f'{result!r}')
    
    wrapper.tracing = True
    return wrapper

#해결 방법: 메타클래스를 사용해서 클래스에 속한 모든 메서드 자동으로 감싸기
import types

trace_types = (
    types.MethodType,
    types.FunctionType,
    types.BuiltinFunctionType,
    types.BuiltinMethodType,
    types.MethodDescriptorType,
    types.ClassMethodDescriptorType)

class TraceMeta(type):
    def __new__(meta, name, bases, class_dict):
        klass = super().__new__(meta, name, bases, class_dict)

        for key in dir(klass):
            value = getattr(klass, key)
            if isinstance(value, trace_types):
                wrapped = trace_func(value)
                setattr(klass, key,wrapped)

        return klass


#TraceMeta는 OhterMeta가 상속하지 않으므로 오류 발생
#메타클래스 상속 활용해도 코드 변경을 못한다.
# 클래스 데코레이션으로 위와 같은 문제 
#클래스 선언 앞에 @기호와 데코레이터 함수 적기
#데코레이터 함수는 인자로 받은 클래스를 적절히 변경하여 재생성
#코드도 짧다

def my_class_decorator(klass):
    klass.extra_param = '안녕'
    return klass

@my_class_decorator
class Myclass:
    pass


def trace(klass):
    for key in dir(klass):
        value = getattr(klass, key)
        if isinstance(value, trace_types):
            wrapped = trace_func(value)
            setattr(klass, key, wrapped)   

    return klass

    
#위의 데코레이터를 우리가 만든 dict 하위 클래스 적용하면
#메타클래스 쓴 것과 같은 결과

@trace
class TraceDict(dict):
    pass

trace_dict = TraceDict([('안녕', 1)])
trace_dict['거기'] = 2
trace_dict['안녕']
try:
    trace_dict['존재하지 않음']

except KeyError:
    pass #키 오류가 발생할 것을 예상

    
__new__((<class '__main__.TraceDict'>, [('안녕', 1)]), {}) ->{}
__getitem__(({'안녕': 1, '거기': 2}, '안녕'), {}) ->1
__getitem__(({'안녕': 1, '거기': 2}, '존재하지 않음'), {}) ->KeyError('존재하지 않음')

#데코레이션을 적용할 클래스에 이미 메타 클래스가 있어도 데코레이터 사용 가능
#이런 식으로 클래스 합성 및 확장이 일어남
class OtherMeta(type):
    pass

@trace
class TraceDict(dict, metaclass = OtherMeta):
    pass

trace_dict = TraceDict([('안녕', 1)])
trace_dict['거기'] = 2
trace_dict['안녕']

try:
    trace_dict['존재하지 않음']
except KeyError:
    pass

```

코드를 입력하세요
new((<class 'main.TraceDict'>, [('안녕', 1)]), {}) ->{}
getitem(({'안녕': 1, '거기': 2}, '안녕'), {}) ->1
getitem(({'안녕': 1, '거기': 2}, '존재하지 않음'), {}) ->KeyError('존재하지 않음')


<br>

### Summary

- 클래스 데코레이터는 class 인스턴스를 파라미터로 받아서 이 클래스를 변경한 클래스나 새로운 클래스를 반환해주는 간단한 함수

- 준비 코드를 최소화하면서 클래스 내부의 모든 메서드나 애트리뷰트를 변경하고 싶을 때 클래스 데코레이터가 유용

- 메타클래스는 서로 쉽게 합성할 수 없지만, 여러 클래스 데코레이터를 충돌없이 사용해서 똑같은 클래스를 확장
profile
성장을 도울 아카이빙 블로그

0개의 댓글