Python Metaclass 개념과 활용
Python의 metaclass 개념을 이해하고, 실전에서 metaclass를 활용하는 방법을 알아봅니다.
Python Metaclass 개념과 활용
Metaclass는 클래스를 만드는 클래스입니다. Python에서 모든 클래스는 metaclass의 인스턴스이며, metaclass를 이해하면 동적 클래스 생성과 같은 고급 기능을 활용할 수 있습니다.
Environment
$ python --version
Python 3.11.5
$ python -c "print(type(type))"
Problem: 동적 클래스 생성 필요
다음과 같은 상황에서 metaclass가 필요합니다:
# 자동으로 필드 검증 로직을 추가하고 싶을 때
class User:
name = "Joel"
age = 34
# 이런 로직을 자동화하고 싶음
# class User(metaclass=ValidationMeta):
# name: str
# age: intAnalysis: Metaclass 동작 원리
# Python에서 클래스 생성 과정:
# 1. class 문장을 만나면 type.__call__이 실행됨
# 2. type.__call__은 __new__와 __init__을 호출
# 3. metaclass의 __new__에서 클래스 객체 생성
# 4. metaclass의 __init__에서 클래스 초기화
# 확인
class MyClass:
pass
print(type(MyClass)) #
print(type(MyClass())) # Solution: Metaclass 활용법
기본 Metaclass
class UpperAttrMeta(type):
def __new__(cls, name, bases, dct):
# Automatically uppercase all attributes
uppercase_attrs = {}
for attr_name, attr_value in dct.items():
if not attr_name.startswith('_'):
uppercase_attrs[attr_name.upper()] = attr_value
else:
uppercase_attrs[attr_name] = attr_value
return super().__new__(cls, name, bases, uppercase_attrs)
class MyClass(metaclass=UpperAttrMeta):
foo = 'bar'
# Access with uppercase
print(MyClass.FOO) # 'bar'검증 Metaclass
class ValidatedMeta(type):
def __new__(cls, name, bases, dct):
# Get annotations for type checking
annotations = dct.get('__annotations__', {})
for attr_name, expected_type in annotations.items():
if attr_name.startswith('_'):
continue
# Create property with validation
def make_validator(attr, typ):
def getter(self):
return getattr(self, f'_{attr}', None)
def setter(self, value):
if not isinstance(value, typ):
raise TypeError(
f"{attr} must be {typ.__name__}, "
f"got {type(value).__name__}"
)
setattr(self, f'_{attr}', value)
return property(getter, setter)
dct[attr_name] = make_validator(attr_name, expected_type)
return super().__new__(cls, name, bases, dct)
class User(metaclass=ValidatedMeta):
name: str
age: int
email: str
# Usage
user = User()
user.name = "Joel" # OK
user.age = 34 # OK
try:
user.age = "thirty-four" # TypeError
except TypeError as e:
print(f"Error: {e}")Singleton Metaclass
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class Database(metaclass=SingletonMeta):
def __init__(self):
self.connection = "Connected to database"
def query(self, sql):
return f"Executing: {sql}"
# Only one instance is created
db1 = Database()
db2 = Database()
print(db1 is db2) # True
print(id(db1) == id(db2)) # TrueRegistry Metaclass
class PluginMeta(type):
_registry = {}
def __new__(cls, name, bases, dct):
new_class = super().__new__(cls, name, bases, dct)
# Register non-base classes
if name != 'BasePlugin':
cls._registry[name] = new_class
return new_class
@classmethod
def get_plugin(cls, name):
return cls._registry.get(name)
@classmethod
def get_all_plugins(cls):
return dict(cls._registry)
class BasePlugin(metaclass=PluginMeta):
def execute(self):
raise NotImplementedError
class EmailPlugin(BasePlugin):
def execute(self):
return "Sending email"
class SMSPlugin(BasePlugin):
def execute(self):
return "Sending SMS"
# Usage
plugin_class = PluginMeta.get_plugin('EmailPlugin')
plugin = plugin_class()
print(plugin.execute()) # Sending email
print(PluginMeta.get_all_plugins().keys())
# dict_keys(['EmailPlugin', 'SMSPlugin'])Advanced: 메타 클래스와 데코레이터 비교
# Metaclass approach
class ValidatedMeta(type):
def __new__(cls, name, bases, dct):
# Validation logic here
return super().__new__(cls, name, bases, dct)
class User(metaclass=ValidatedMeta):
name: str
# Decorator approach (simpler)
def validated(cls):
# Validation logic here
return cls
@validated
class User:
name: str
#何时选择哪个?
# - Metaclass: 클래스 생성 자체를 제어할 때
# - Decorator: 클래스의 동작을 수정할 때Lessons Learned
Metaclass는 type의 인스턴스: 모든 클래스는 metaclass의 인스턴스이며, 기본 metaclass는
type입니다.new 메서드: 클래스 생성 시
__new__메서드에서 클래스의 속성과 메서드를 수정할 수 있습니다.과도한 사용 지양: metaclass는 강력하지만, 복잡성을 증가시킵니다. 필요한 경우에만 사용하세요.
대안 고려: decorator, mixin, descriptor 등 metaclass 없이도 달성할 수 있는 방법이 있는지 먼저 확인하세요.
Python 3.6+: Python 3.6부터 클래스 정의에
__init_subclass__를 사용하여 metaclass 없이도 유사한 기능을 구현할 수 있습니다.
This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.