~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 이 타입이 type 인 Foo 라는 객체 생성. 인스턴스가 클래스 호출로 만들어지듯, 클래스는 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 는 진짜 필요할 때만.
Singleton metaclass 구현 — 사용 클래스가 단 하나의 인스턴스만 — 생성자 반복 호출이 같은 인스턴스 반환. 그 다음 싱글톤 패턴 다시 짜 — (a) 클래스 데코레이터로, (b) __new__ 사용. 셋 비교 — 어느 게 가장 명확? 두 인스턴스 만들고 is 로 같은 객체 확인.
Progress
Progress is local-only — sign in to sync across devices.