__ set_name __ 를 활용하기 이전에, 해당 디스크립터가 필요하게 된 상황을 알아보자
class Field:
def __init__(self, name):
self.name = name
self.internal_name = '_' + self.name
def __get__(self, instance, instance_type):
if instance is None:
return self
return getattr(instance, self.internal_name, '')
def __set__(self, instance, value):
setattr(instance, self.internal_name, value)
class Customer:
first_name = Field('first_name')
last_name = Field('last_name')
prefix = Field('prefix')
suffix = Field('suffix')
위 코드로 데이터를 표현해주는 객체(Customer)의 column를 Field 라는 프로퍼티 클래스를 만들어주었다.
하지만 이 방법에는 first_name 이라는 변수로 네임을 표현해줬는데
Field에 다시 네임을 다시 한번 기재해줘야 하는 중복이 발생하게 되었다. ex: Field('first_name')
이 문제를 해결하기 위해서 first_name이라는 변수를 어떻게든 활용하면 될거라고 생각할 수 있지만,
파이썬에서는 오른쪽에서 왼쪽으로 실행하는 구조를 갖고 있어
Field()가 먼저 실행 후 변수에 값이 할당된다. 이를 해결하기 위해서 메타클래스를 사용해볼 수 있는데
class Meta(type):
def __new__(meta, name, bases, class_dict):
for key, value in class_dict.items():
if isinstance(value, Field):
value.name = key
value.internal_name = '_' + key
cls = type.__new__(meta, name, bases, class_dict)
return cls
class DatabaseRow(metaclass=Meta):
pass
class Field:
def __init__(self):
# 이 부분을 메타클래스에서 채워주게 됩니다!
self.name = None
self.internal_name = None
def __get__(self, instance, instance_type):
if instance is None:
return self
return getattr(instance, self.internal_name, '')
def __set__(self, instance, value):
setattr(instance, self.internal_name, value)
class BetterCustomer(DatabaseRow):
first_name = Field()
last_name = Field()
prefix = Field()
suffix = Field()
위 코드로 리팩토링하면서 생성자에 컬럼이름을 받지 않게 되었고,
메타클래스에서 애트리뷰트를 설정함으로써 중복을 없앨 수 있게 되었다
하지만 위 코드의 문제점은 DatabaseRow를 무조건 상속받아야 함으로부터 제약이 생기는데,
이 부분을 디스크럽터의 __ set_name __ 호출함으로써
메타클래스에서 애트리뷰트를 통제하는 로직을 리팩토링할 수 있게 된다.
class Field:
def __init__(self):
self.name = None
self.internal_name = None
# 디스크립터 인스턴스를 소유 중인 클래스와 대입될 에트리뷰트 이름을 인자로 받는다
def __set_name__(self, owner, name):
# 클래스 생성 시 모든 스크립터에 대해 이 메서드 호출
self.name = name
self.internal_name = '_' + name
def __get__(self, instance, instance_type):
if instance is None:
return self
return getattr(instance, self.internal_name, '')
def __set__(self, instance, value):
setattr(instance, self.internal_name, value)
위 과정들을 통해 메타클래스가 어떤 클래스가 정의되기 전에 클래스의 애트리뷰트를 변경할 수 있고,
__ set_name __ 메서드로 디스크립터 클래스에 정의하면
디스크립터가 포함한 프로퍼티 이름을 처리할 수 있음을 알 수 있다.
디스크립터가 변경한 클래스 인스턴스 딕셔너리에 데이터 저장하게 만들면 누수를 피할 수 있고,
weakref 내장매서드를 쓰지 않아도 된다
'프로그래밍 언어 > Python' 카테고리의 다른 글
GIL를 무조건 신뢰하면 안되는 이유( feat.스레드 감수성 ) (0) | 2023.09.15 |
---|---|
두 코드의 차이점을 설명하시요( feat. Python,쓰레드, GIL, 병렬 I/O) (0) | 2023.09.15 |
클래스를 디버깅해보자(feat.메타클래스,클래스 데코레이터) (0) | 2023.09.15 |
메타클래스와 init_subclass (0) | 2023.09.12 |
@classmethod 는 왜 쓸까? (0) | 2023.09.12 |