클래스는 객체다
파이썬에서 클래스는 단순한 템플릿 이상의 존재입니다. 모든 클래스는 실제로 객체이며, 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
메타클래스 방식은 클래스 생성 시점에 단일 인스턴스를 관리하므로, 모든 서브클래스가 자동으로 단일 인스턴스를 가집니다.