C.W.K.
Stream
Lesson 04 of 05 · published

Metaclass — 클래스를 만드는 클래스

~22 min · metaclass, type, class-creation, advanced

Level 0호기심
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete

클래스도 객체야

Python 에서 클래스 자체가 객체. class Foo: 쓰면 Python 이 타입이 typeFoo 라는 객체 생성. 인스턴스가 클래스 호출로 만들어지듯, 클래스는 type 호출로 만들어져. Foo = type("Foo", (), {}) 가 수동 클래스 정의. type 은 metaclass — "클래스의 클래스".

Metaclass 가 가능하게 하는 것

클래스 정의 시점 동작 커스터마이즈. 클래스 본문 검증, 메서드 변경, 클래스 어디 등록, 자동 속성 추가 가능. 프레임워크들이 이런 거에 metaclass 사용 — Django ORM (class User(Model): 정의가 SQL 기계 연결), 옛 Pydantic, abstract base class 강제.

비용 — 그리고 대부분 코드가 안 필요한 이유

Metaclass 는 Python 이 제공하는 가장 침습적 커스터마이제이션. 클래스 정의 시점에 돌고, 호출자가 클래스 본문에서 못 보는 방식으로 동작 변경, 결합 시 의외 상호작용. Tim Peters — "Metaclass 는 99% 사용자가 신경쓸 필요 없는 깊은 마법. 필요한지 의심하면 안 필요한 거."

현대 대안

클래스 데코레이터가 metaclass 하는 일 대부분 — 생성된 클래스 받고 변경 가능. __init_subclass__ (3.6+) 가 서브클래스 생성 시 자동 실행되는 hook. __set_name__ (3.6+) 이 descriptor 가 붙을 때 도는 hook. 역사적으로 metaclass 부른 대부분 use case 가 이제 이 셋 중 하나가 맞는 도구.

War Story: Pippa 의 어댑터 ABC 가 metaclass 였을 수도. 아닌 이유 — abc.ABCMeta (abc.ABC 뒤의 metaclass) 가 "서브클래스가 추상 메서드 구현 강제" 동작 처리, ABC 가 metaclass 우리한테서 숨겨. 교훈 — 대부분 metaclass 필요는 stdlib 헬퍼가 이미 해결.

Code

type 을 metaclass 로 — 수동 클래스 생성·python
# 둘은 같음

# 형태 1
class Foo:
    x = 10
    def hello(self):
        return "hi"

# 형태 2
Foo2 = type("Foo2", (), {"x": 10, "hello": lambda self: "hi"})

f1 = Foo()
f2 = Foo2()
print(f1.x, f1.hello())
print(f2.x, f2.hello())

print(type(Foo))         # <class 'type'>
print(type(Foo2))        # <class 'type'>
단순 커스텀 metaclass·python
class UpperAttrMeta(type):
    def __new__(mcs, name, bases, namespace):
        upper_namespace = {}
        for key, value in namespace.items():
            if not key.startswith("__"):
                upper_namespace[key.upper()] = value
            else:
                upper_namespace[key] = value
        return super().__new__(mcs, name, bases, upper_namespace)

class Foo(metaclass=UpperAttrMeta):
    x = 10
    def hello(self):
        return "hi"

print(Foo.X)             # 10  — 자동 rename
print(Foo().HELLO())     # 'hi'  — 메서드도 rename
__init_subclass__ — 대부분 metaclass 용도 대체·python
class Plugin:
    registry = {}

    def __init_subclass__(cls, name=None, **kwargs):
        super().__init_subclass__(**kwargs)
        Plugin.registry[name or cls.__name__] = cls

class JsonPlugin(Plugin, name="json"):
    def process(self, data):
        import json
        return json.dumps(data)

class YamlPlugin(Plugin, name="yaml"):
    def process(self, data):
        return f"yaml: {data}"

print(Plugin.registry)
# {'json': <class '...JsonPlugin'>, 'yaml': <class '...YamlPlugin'>}

# 서브클래스가 자동 등록 — metaclass 불필요
클래스 데코레이터 — metaclass 용도 많이 대체·python
def auto_repr(cls):
    def __repr__(self):
        attrs = ", ".join(f"{k}={v!r}" for k, v in self.__dict__.items())
        return f"{cls.__name__}({attrs})"
    cls.__repr__ = __repr__
    return cls

@auto_repr
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

print(Point(3, 4))         # Point(x=3, y=4)

# 옛날엔 metaclass 연습이었어. 이제 4 줄짜리 데코레이터.
# 대부분 use case — 이거 선호. metaclass 는 진짜 필요할 때만.

External links

Exercise

Singleton metaclass 구현 — 사용 클래스가 단 하나의 인스턴스만 — 생성자 반복 호출이 같은 인스턴스 반환. 그 다음 싱글톤 패턴 다시 짜 — (a) 클래스 데코레이터로, (b) __new__ 사용. 셋 비교 — 어느 게 가장 명확? 두 인스턴스 만들고 is 로 같은 객체 확인.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.