메타클래스를 활용하여 새로운 하위 클래스가 정의될 때마다 검증 코드를 수행하는 신뢰성 있는 방법 제공
#메타클래스는 type를 상속해 정의
#메타클래스는 __new__ 메서드를 통해서 자신과 연관된 클래스 내용
# 타입이 실제로 구성 전 클래스 정보를 살펴보고 변경
class Meta(type):
def __new__(meta, name, bases, class_dict):
print(f'* 실행: {name}의 메타 {meta}.__new__')
print('기반 클래스들:', bases)
print(class_dict)
return type.__new__(meta, name, bases, class_dict)
class MyClass(metaclass=Meta):
stuff = 123
def foo(self):
pass
class MySubclass(MyClass):
other = 567
def bar(self):
pass
* 실행: MyClass의 메타 <class '__main__.Meta'>.__new__
기반 클래스들: ()
{'__module__': '__main__', '__qualname__': 'MyClass', 'stuff': 123, 'foo': <function MyClass.foo at 0x7fbcee70bd40>}
* 실행: MySubclass의 메타 <class '__main__.Meta'>.__new__
기반 클래스들: (<class '__main__.MyClass'>,)
{'__module__': '__main__', '__qualname__': 'MySubclass', 'other': 567, 'bar': <function MySubclass.bar at 0x7fbcee70b7a0>}
#연관된 클래스 정의가 되기 전에 이 클래스의 모든 파라미터 검증 by Meta.__new__기능
#검증을 위한 특별 메타 클래스 정의
#다격형 클래스 계층 구조
class ValidatePolygon(type):
def __new__(meta, name, bases, class_dict):
#Polygon 클래스의 하위 클래스만 검증
if bases:
if class_dict['sides'] < 3: #2개 이하의 클래스이면 실행되지 않는다.
raise ValueError('다각형 변은 3개 이상이어야 함')
return type.__new__(meta, name, bases, class_dict)
class Polygon(metaclass =ValidatePolygon):
slides = None #하위 클래스는 이 애트리뷰트에 값을 지정해야 한다
@classmethod
def interior_angles(cls):
return (cls.sides -2) * 180
class Triangle(Polygon):
sides = 3
class Rectangle(Polygon):
sides = 4
class Nonagon(Polygon):
sides = 9
assert Triangle.interior_angles() == 180
assert Rectangle.interior_angles() == 360
assert Nonagon.interior_angles() ==1260
메타클래스를 구현하는 게 시각적으로 코드의 복잡성을 늘린다.
그래서, 파이썬 3.6부터는 init_subclass 특별 클래스 메서드 정의
#코드가 훨씬 간결
class BetterPolygon:
sides = None #하위클래스에서 이 애트리뷰트의 값 지정
def __init_subclass__(cls):
super().__init_subclass__()
if class_dict['sides'] < 3: #2개 이하의 클래스이면 실행되지 않는다.
raise ValueError('다각형 변은 3개 이상이어야 함')
return type.__new__(meta, name, bases, class_dict)
@classmethod
def interior_angles(cls):
return (cls.sides-2)* 180
class Hexagon(BetterPolygon):
sides = 6
assert Hexagon.interior_angles() == 720
#두 개의 메타클래스 활용
#합성성을 해치긴한다.
class ValidatePolygon(type):
def __new__(meta, name, bases, class_dict):
#루트 클래스가 아닌 경우만 검증
if not class_dict.get('is_root'):
if class_dict['sides'] < 3:
raise ValueError('다각형 변은 3개 이상이어야 함')
return type.__new__(meta, name, bases, class_dict)
class Polygon(metaclass=ValidatePolygon):
is_root = True
sides = None #하위 클래스에서 애트리뷰트 지정
class ValidateFilledPolygon(ValidatePolygon):
def __new__(meta, name, bases, class_dict):
#루트 클래스가 아닌 경우만 검증
if not class_dict.get('is_root'):
if class_dict['color'] not in ('red', 'green'):
raise ValueError('지원하지 않는 color 값')
return super().__new__(meta, name, bases, class_dict)
class FilledPolygon(Polygon, metaclass= ValidatePolygon):
is_root = True
color = None
#위와 같이 정의하면 모든 FilledPolygon은 Polygon의 스턴스
class GreenPentagon(FilledPolygon):
color = 'green'
sides = 5
greenie = GreenPentagon()
assert isinstance(greenie, Polygon)
#색 검증
class OrangePentagon(FilledPolygon):
color = 'orange'
sides = 5
메타클래스의 new 메서드는 class문의 모든 본문이 처리된 직후 호출
메타클래스를 사용해 클래스가 정의된 직후이면 클래스가 생성되기 직전인 시점에 클래스의 정의 변경
메타클래스는 원하는 목적을 달성하긴 너무 복잡
init_subclass를 사용해 하위 클래스가 정의된 직후, 하위 클래스 타입이 만들어지기 직전에 해당 클래스가 원하는 요건 확인
init_subclass 정의 안에서 super().init_subclass를 호출해서 여러 계층에 걸쳐서 클래스를 검증하고 다중 상속을 제대로 처리