'Better way 48. __init_subclass__를 사용해 하위 클래스를 검증하라' 정리

Effective Python 2nd 파이썬 코딩의 기술 (교보문고 링크)을 제대로 이해하고자 블로그에 정리합니다.



현재 위치

Note
<6. Metaclasses and Attributes>
Item 48: Validate Subclasses with __init_subclass__
Better way 48. __init_subclass__를 사용해 하위 클래스를 검증하라



하위 클래스가 제대로 구현됐는지 확인하기 위해서는 __init_subclass__를 사용합니다. (파이썬 3.6에서 추가됨)

메타클래스를 정의하는 방법이 있었지만, 요게 훨씬 낫습니다.



하위 클래스의 sides 가 3 이상인지 검증하는 예제입니다.

# Example 4
class BetterPolygon:
    sides = None  # Must be specified by subclasses

    def __init_subclass__(cls):
        print(f'* Running __init_subclass__ for {cls}')
        super().__init_subclass__()
        if cls.sides < 3:
            raise ValueError('Polygons need 3+ sides')

    @classmethod
    def interior_angles(cls):
        return (cls.sides - 2) * 180


print('\n== Before BetterPolygon')
class Hexagon(BetterPolygon):
    sides = 6
print('Hexagon', Hexagon.interior_angles())  # Hexagon 720


print('\n== Before Point')
try:
    class Point(BetterPolygon):
        sides = 1

except ValueError as e:
    print('ValueError!!!', e)  # ValueError!!! Polygons need 3+ sides
== Before BetterPolygon
* Running __init_subclass__ for <class '__main__.Hexagon'>
Hexagon 720

== Before Point
* Running __init_subclass__ for <class '__main__.Point'>
ValueError!!! Polygons need 3+ sides

BetterPolygon을 상속한 클래스를 정의하면, __init_subclass__ 가 불려서 클래스가 잘 정의되었는지 확인할 수 있습니다.



책에서 챕터 마지막 부분에 적혀있는 내용입니다.

Tip
The __new__ method of metaclasses is run after the class statement’s entire body has been processed.
메타클래스의 __new__ 메서드는 class 문의 모든 본문이 처리된 직후에 호출된다.
Tip
Metaclasses can be used to inspect or modify a class after it’s defined but before it’s created, but they’re often more heavyweight than what you need.
메타클래스를 사용해 클래스가 정의된 직후이면서 클래스가 생성되기 직전인 시점에 클래스 정의를 변경할 수 있다. 하지만 메타클래스는 원하는 목적을 달성하기에 너무 복잡해지는 경우가 많다.
Tip
Use __init_subclass__ to ensure that subclasses are well formed at the time they are defined, before objects of their type are constructed.
__init_subclass__를 사용해 하위 클래스가 정의된 직후, 하위 클래스 타입이 만들어지기 직전에 해당 클래스가 원하는 요건을 잘 갖췄는지 확인하라.
Tip
Be sure to call super().__init_subclass__ from within your class’s __init_subclass__ definition to enable validation in multiple layers of classes and multiple inheritance.
__init_subclass__ 정의 안에서 super().__init_subclass__를 호출해 여러 계층에 걸쳐 클래시를 검증하고 다중 상속을 제대로 처리하도록 하라.


__init_subclass__를 사용할 거지만, 예전의 메타클래스 방식도 알아두도록 합시다.

from pprint import pprint


# Example 2
class ValidatePolygon(type):  # 메타클래스 정의
    def __new__(meta, name, bases, class_dict):
        # Only validate subclasses of the Polygon class
        print(f'* Running {meta}.__new__ for {name}')
        print('Bases:', bases)
        pprint(class_dict)
        if bases:
            if class_dict['sides'] < 3:
                raise ValueError('Polygons need 3+ sides')
        return type.__new__(meta, name, bases, class_dict)


print('\n== Before Polygon')
class Polygon(metaclass=ValidatePolygon):
    sides = None  # Must be specified by subclasses

    @classmethod
    def interior_angles(cls):
        return (cls.sides - 2) * 180


print('\n== Before Triangle')
class Triangle(Polygon):
    sides = 3
print('Triangle', Triangle.interior_angles())


print('\n== Before Line')
try:
    class Line(Polygon):
        print('Before sides')
        sides = 2
        print('After sides')  # 여기도 불림
except ValueError as e:
    print('ValueError!!', e)

메타클래스를 상속받은 클래스의 정의 코드가 실행되면, 메타 클래스의 __new__가 실행되고,
여기에서 클래스가 잘 구현되었는지 검증할 수 있습니다.

== Before Polygon
* Running <class '__main__.ValidatePolygon'>.__new__ for Polygon
Bases: ()
{'__module__': '__main__',
 '__qualname__': 'Polygon',
 'interior_angles': <classmethod object at 0x00000263CF7A91F0>,
 'sides': None}

== Before Triangle
* Running <class '__main__.ValidatePolygon'>.__new__ for Triangle
Bases: (<class '__main__.Polygon'>,)
{'__module__': '__main__', '__qualname__': 'Triangle', 'sides': 3}
Triangle 180

== Before Line
Before sides
After sides
* Running <class '__main__.ValidatePolygon'>.__new__ for Line
Bases: (<class '__main__.Polygon'>,)
{'__module__': '__main__', '__qualname__': 'Line', 'sides': 2}
ValueError!! Polygons need 3+ sides

sides 값이 2가 들어와서 ValueError 를 발생시켰습니다.





Related Content