본문 바로가기
Dev/Python

파이썬 심화: 클래스를 만드는 클래스, '메타클래스(Metaclass)'

by DevGyu0511 2026. 1. 20.
반응형

MetaClass

개요

파이썬에서는 "모든 것이 객체(Object)다"라는 철학이 흐른다. 우리가 흔히 사용하는 숫자, 문자열, 함수는 물론이고, 심지어 우리가 정의한 '클래스' 그 자체도 객체이다. 그렇다면 객체를 만드는 것이 클래스라면, 클래스를 만드는 것은 무엇일까? 그 정답이 바로 메타클래스이다.


1. 메타클래스란 무엇인가?

메타클래스는 단어 그대로 '클래스의 클래스'이다. 붕어빵틀(클래스)이 붕어빵(인스턴스)을 찍어낸다면, 메타클래스는 그 '붕어빵틀' 자체를 만들어내는 원형 틀이라고 볼 수 있다.

붕어빵으로 이해하는 계층 구조

  • 인스턴스(붕어빵): 클래스라는 틀에서 구워져 나온 실제 객체.
  • 클래스(붕어빵 틀): 붕어빵을 만들기 위한 설계도이자 틀.
  • 메타클래스(붕어빵 틀을 만드는 틀): 붕어빵 틀 자체를 설계하고 제작하는 원형 틀.

간단 사용 예제

class Hello:
    pass

h = Hello()

print(type(h))      # <class '__main__.Hello'> (h의 부모는 Hello)
print(type(Hello))  # <class 'type'> (Hello의 부모는 type 메타클래스)

설명: Hello 클래스 자체의 타입을 확인하면 <class 'type'>이 나온다. 이는 파이썬의 모든 클래스가 사실 type이라는 거대한 '원형 틀'에 의해 만들어진 객체임을 증명한다.


2. type() 함수의 두 가지 얼굴

type() 함수는 파이썬에서 가장 독특한 존재 중 하나이다. 단순히 객체의 자료형을 확인하는 기능을 넘어, 그 자체가 모든 클래스의 조상인 기본 메타클래스로 작동한다.

2.1. 객체의 타입 확인

특정 인스턴스가 어떤 클래스 소속인지 알려주는 가장 일반적인 방식이다.

n = 10
print(type(n))  # <class 'int'>

2.2. 동적 클래스 생성 (메타클래스로서의 type)

type()은 인자를 3개 전달받아 새로운 클래스를 즉석에서 설계하고 만들어낼 수 있다. 우리가 class 키워드를 사용하는 것은 사실 이 과정을 편하게 쓰기 위한 Syntactic Sugar에 불과하다.

# type(이름, 상속부모, 속성딕셔너리)
User = type('User', (), {'level': 1})

player = User()
print(player.level)  # 1 출력

3. 커스텀 메타클래스: 클래스 생성에 개입하기

type을 상속받아 직접 메타클래스를 정의하면, 클래스가 메모리에 올라가는 '찰나'에 개입하여 구조를 강제로 바꾸거나 규격을 검증할 수 있다. 즉, 특정 멤버 변수가 없으면 클래스 생성을 거부하는 등의 제어가 가능하다.

class VerifyMeta(type):
    def __new__(cls, name, bases, attrs):
        # 1. 검증: 클래스에 'required_attr'이라는 멤버 변수가 없으면 생성 거부
        if 'required_attr' not in attrs:
            raise TypeError(f"{name} 클래스는 'required_attr' 멤버 변수를 반드시 포함해야 한다.")

        # 2. 구조 변경: 모든 클래스 이름을 대문자로 강제 변환
        return super().__new__(cls, name.upper(), bases, attrs)

# 정상 생성 (필수 멤버 변수 존재)
class Success(metaclass=VerifyMeta):
    required_attr = True

# TypeError 발생 (검사 로직에 걸려 클래스 탄생 실패)
# class Fail(metaclass=VerifyMeta):
#     pass

4. 메타클래스는 언제 사용하는가?

메타클래스는 매우 강력한 도구이므로 실질적인 제어가 필요할 때 사용한다.

4.1. API 규격 강제 (Validation)

라이브러리 설계 시, 사용자가 만든 클래스가 특정 규칙을 따르는지 검사한다.

class MethodCheckMeta(type):
    def __new__(cls, name, bases, attrs):
        # 메서드 중 하나라도 'test_'로 시작하지 않으면 생성 제한
        for attr_name in attrs:
            if callable(attrs[attr_name]) and not attr_name.startswith('test_') and not attr_name.startswith('__'):
                raise TypeError(f"메서드 '{attr_name}'의 이름은 반드시 'test_'로 시작해야 한다.")
        return super().__new__(cls, name, bases, attrs)

class MyTest(metaclass=MethodCheckMeta):
    def test_run(self): pass  # 통과
    # def run_fast(self): pass # 활성화 시 TypeError 발생

4.2. 자동 속성 등록 및 수정

클래스 정의 시점에 속성 이름을 자동으로 인지하여 관리할 때 사용한다.

class ModelMeta(type):
    def __new__(cls, name, bases, attrs):
        # 클래스 내부의 필드들을 자동으로 리스트화하여 관리
        attrs['_fields'] = [k for k, v in attrs.items() if not k.startswith('__')]
        return super().__new__(cls, name, bases, attrs)

class User(metaclass=ModelMeta):
    name = "Admin"
    email = "admin@test.com"

print(User._fields)  # ['name', 'email'] 자동 등록 확인

4.3. 싱글톤(Singleton) 패턴 구현

인스턴스가 오직 하나만 생성되도록 클래스 생성 로직을 제어한다.

class SingletonMeta(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]

class Database(metaclass=SingletonMeta):
    pass

5. 결론

파이썬 객체지향 철학의 완성

메타클래스를 이해한다는 것은 파이썬이 클래스를 어떻게 바라보는지 이해하는 것과 같다. 클래스조차 객체로 관리하고, 그 생성 로직조차 개발자가 제어할 수 있게 열어둔 이 유연함이 바로 파이썬의 원동력이다.

"만약 당신이 메타클래스가 필요한지 고민하고 있다면, 당신은 그것이 필요하지 않은 것이다."
- Tim Peters -

반응형