하위_클래스를_검증

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

이어드림

목록 보기
100/146

initsubclass__를 사용해서 하위 클래스를 검증하라

메타클래스를 활용하여 새로운 하위 클래스가 정의될 때마다 검증 코드를 수행하는 신뢰성 있는 방법 제공

#메타클래스는 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

Summary

  • 메타클래스의 new 메서드는 class문의 모든 본문이 처리된 직후 호출

  • 메타클래스를 사용해 클래스가 정의된 직후이면 클래스가 생성되기 직전인 시점에 클래스의 정의 변경

  • 메타클래스는 원하는 목적을 달성하긴 너무 복잡

  • init_subclass를 사용해 하위 클래스가 정의된 직후, 하위 클래스 타입이 만들어지기 직전에 해당 클래스가 원하는 요건 확인

  • init_subclass 정의 안에서 super().init_subclass를 호출해서 여러 계층에 걸쳐서 클래스를 검증하고 다중 상속을 제대로 처리

profile
성장을 도울 아카이빙 블로그

0개의 댓글