파이썬의 메타클래스 이해 및 단일 인스턴스 패턴 구현

클래스는 객체다

파이썬에서 클래스는 단순한 템플릿 이상의 존재입니다. 모든 클래스는 실제로 객체이며, class 키워드를 사용할 때 파이썬은 해당 클래스에 대한 객체를 메모리에 생성합니다. 이는 다음과 같은 예시로 확인할 수 있습니다:

class Example(object):
    pass

instance = Example()
print(instance)  # <__main__.Example object at 0x...>

이러한 클래스 객체는 일반적인 객체처럼 다룰 수 있습니다. 변수에 할당하거나, 함수 인자로 전달하거나, 속성을 추가할 수 있습니다.

런타임 시 클래스 동적 생성

클래스가 객체이기 때문에, 런타임 시에도 동적으로 생성할 수 있습니다. 다음은 type()을 사용해 클래스를 직접 생성하는 방법입니다:

DynamicClass = type('DynamicClass', (), {'value': 42})
obj = DynamicClass()
print(obj.value)  # 42

또한, 클래스 내부의 메서드나 속성도 동적으로 정의할 수 있습니다:

def method(self):
    return self.value

DynamicClass = type('DynamicClass', (), {'value': 42, 'get_value': method})
obj = DynamicClass()
print(obj.get_value())  # 42

메타클래스란?

메타클래스는 클래스를 생성하는 클래스입니다. 기본적으로 파이썬의 type이 내장된 메타클래스이며, 모든 클래스는 이 메타클래스를 통해 생성됩니다. 예를 들어:

print(type(Example))  # <class 'type'>
print(type(Example()))  # <class '__main__.Example'>

모든 객체는 어떤 클래스의 인스턴스이며, 그 클래스 자체도 또 다른 클래스의 인스턴스입니다. 이 계층 구조는 무한히 계속됩니다.

사용자 정의 메타클래스

메타클래스를 통해 클래스 생성 시 자동으로 특정 로직을 적용할 수 있습니다. 예를 들어, 모든 속성을 대문자로 변환하는 메타클래스를 만들어보겠습니다:

class UpperAttrMeta(type):
    def __new__(cls, name, bases, attrs):
        uppercase_attrs = {
            key.upper(): value for key, value in attrs.items()
            if not key.startswith('__')
        }
        # 원래 속성들을 제거하고 대문자 버전으로 교체
        attrs.update(uppercase_attrs)
        return super().__new__(cls, name, bases, attrs)

class MyClass(metaclass=UpperAttrMeta):
    bar = 'hello'
    baz = 'world'

print(hasattr(MyClass, 'bar'))     # False
print(hasattr(MyClass, 'BAR'))    # True
print(MyClass.BAR)                # hello

ORM 시스템 구현을 통한 메타클래스 활용

메타클래스는 데이터베이스 매핑과 같은 고급 기능을 구현하는 데 유용합니다. 아래는 간단한 ORM 시스템의 예제입니다:

class Field:
    def __init__(self, column_type):
        self.column_type = column_type

class StringField(Field):
    def __init__(self):
        super().__init__('varchar(100)')

class IntegerField(Field):
    def __init__(self):
        super().__init__('bigint')

class ModelMetaclass(type):
    def __new__(cls, name, bases, attrs):
        if name == 'Model':
            return super().__new__(cls, name, bases, attrs)
        
        mappings = {}
        for key, value in attrs.items():
            if isinstance(value, Field):
                mappings[key] = value
        # 클래스 속성에서 필드 제거
        for key in mappings:
            attrs.pop(key)
        attrs['__mappings__'] = mappings
        attrs['__table__'] = name.lower()
        return super().__new__(cls, name, bases, attrs)

class Model(dict, metaclass=ModelMetaclass):
    def save(self):
        fields = []
        params = []
        args = []
        for k, v in self.__mappings__.items():
            fields.append(v.column_type)
            params.append('?')
            args.append(getattr(self, k))
        sql = f"INSERT INTO {self.__table__} ({','.join(fields)}) VALUES ({','.join(params)})"
        print(sql)
        print(args)

class User(Model):
    id = IntegerField()
    name = StringField()
    email = StringField()

u = User(id=1, name='Alice', email='alice@example.com')
u.save()

단일 인스턴스 패턴: __new__ vs 메타클래스

단일 인스턴스 패턴은 하나의 인스턴스만 생성되도록 보장합니다. 두 가지 방식으로 구현할 수 있습니다.

1. __new__ 메서드를 통한 구현

class Singleton:
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super().__new__(cls)
        return cls._instance

s1 = Singleton()
s2 = Singleton()
print(s1 is s2)  # True

2. 메타클래스를 통한 구현

class SingletonMeta(type):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._instance = None

    def __call__(self, *args, **kwargs):
        if self._instance is None:
            self._instance = super().__call__(*args, **kwargs)
        return self._instance

class MyClass(metaclass=SingletonMeta):
    def __init__(self):
        print("Initialized")

a = MyClass()
b = MyClass()
print(a is b)  # True

메타클래스 방식은 클래스 생성 시점에 단일 인스턴스를 관리하므로, 모든 서브클래스가 자동으로 단일 인스턴스를 가집니다.

태그: python 메타클래스 단일 인스턴스 패턴 ORM 클래스 생성

7월 11일 04:22에 게시됨