본문 바로가기
목차
Python

Python Data Model (+ Metaclass)

by ds31x 2026. 2. 25.
728x90
반응형

1. 들어가며: Python은 어떻게 동작하는가?

Python 코드를 작성할 때, 우리는 자연스럽게 다음과 같은 표현들을 사용한다:

a + b           # 두 값을 더한다
len(my_list)    # 리스트의 길이를 구한다
my_list[0]      # 첫 번째 요소에 접근한다
for x in obj:   # 객체를 순회한다
    print(x)

이 코드들이 "그냥 작동한다"고 생각할 수 있다.

하지만 Python은 이 연산들을 어떻게 처리하는 걸까?

 

예를 들어, len([1, 2, 3])을 호출하면 Python은 어떻게 3이라는 답을 알아내는가?

+ 연산자는 숫자도 더하고, 문자열도 이어붙이는데, Python은 이 차이를 어떻게 구분하는가?

 

이 질문들의 답이 바로 Python Data Model이다.

용어정리
프로그래밍에서 Dispatch
호출할 함수나 method를 런타임에 결정하여 실행하는 과정 을 가리킴.


2. Python Data Model이란?

Python Data Model은
Python에서 Object가 언어 구문(syntax)과 상호작용하는 방식을 정의한 규칙 체계이다.

 

쉽게 말해, +, len(), [], for 같은 Python 문법이 실제로 어떤 방식으로 실행되는지를 정의한 약속이다.

 

핵심 아이디어는 다음과 같다:

Python의 연산자와 내장 함수는 직접 자신이 실행되는 것이 아니라,
관련된 Object에 정의된 special method를 호출하는 방식으로 동작한다.

 

때문에 Python 에서 dispathch

  • Python interpreter가 특정 연산 등을 수행할 때,
  • 해당 object의 Type에서 적절한 method를 찾아 호출하는 과정 이라고 볼 수 있음.

3. Special Method: dunder methods

3.1 Special Method란?

Special method(또는 dunder method, magic method)는 이름이 double underscore(__)로 감싸진 특별한 method이다:

__init__, __len__, __add__, __getitem__, __str__, ...
  • 이 method들은 Python interpreter와의 약속이다.
  • 특정 연산이 수행될 때, Python은 해당 object에서 약속된 special method를 찾아 호출한다.

3.2 예제: len() 함수의 동작 원리

my_list = [1, 2, 3]
print(len(my_list))  # 3

 

len(my_list)를 호출하면, Python은 내부적으로 다음을 수행한다:

type(my_list).__len__(my_list)

 

즉, my_list의 type인 list class에 정의된 __len__ method를 호출한다.

 

이것이 우리가 직접 custom class를 만들 때 len()을 지원할 수 있는 이유이다:

class MyCollection:
    def __init__(self, items):
        self.items = items

    def __len__(self):
        return len(self.items)

c = MyCollection([1, 2, 3, 4, 5])
print(len(c))  # 5

3.3 주요 Syntax와 Special Method 매핑

Syntax / 함수 호출되는 Special Method
a + b type(a).__add__(a, b)
a - b type(a).__sub__(a, b)
a * b type(a).__mul__(a, b)
len(obj) type(obj).__len__(obj)
obj[i] type(obj).__getitem__(obj, i)
obj[i] = v type(obj).__setitem__(obj, i, v)
obj() type(obj).__call__(obj)
str(obj) type(obj).__str__(obj)
for x in obj type(obj).__iter__(obj)

이 표를 암기할 필요는 없음.

 

보다 자세한 건 다음 url을 참고:

2023.07.13 - [Python] - [Python] special methods and operator overloading

 

[Python] special methods and operator overloading

Special Methods사용자가 직접 호출하는 경우가 거의 없고, 간접적으로 호출이 됨.즉, 개발자(사용자)가 over-riding을 통해 구현은 하지만 직접 호출하는 경우가 거의 없고,개발자가 다른 built-in function

ds31x.tistory.com

 

중요한 건 다음임:

Python의 syntax는
해당 object의 Type에 정의된 special method로 변환되어 실행된다.


4. 일반 Method vs Special Method

Class에는 일반 method와 special method 모두 정의할 수 있다.

이들은 코드상으로는 이름만 다를 뿐이지만, 호출 방식이 근본적으로 다르다.

 

물론 둘에 대한 dispatch를
Python Data Model에서 정의하고 있음.

4.1 일반 Method: 사용자가 직접 호출

class Calculator:
    def add(self, a, b):
        return a + b

calc = Calculator()
calc.add(1, 2)  # 사용자가 직접 호출

일반 method의 특징:

  • 사용자 코드가 직접 호출한다.
  • Attribute lookup 규칙을 따른다 (Instance → Class → 상위 Class 순으로 탐색).
  • Instance에 동적으로 재정의 (Instance-level overriding)할 수 있다.
calc.add = lambda a, b: a * b  # instance에 동적 할당
calc.add(2, 3)  # 6 (재정의된 함수가 호출됨)

4.2 Special Method: Interpreter가 호출

class MyNumber:
    def __init__(self, value):
        self.value = value

    def __add__(self, other):
        return MyNumber(self.value + other.value)

a = MyNumber(10)
b = MyNumber(20)
c = a + b  # interpreter가 __add__를 호출

Special method의 특징:

  • Python interpreter가 호출한다.
  • 일반 attribute lookup을 사용하지 않는다.
  • Type(Class)에 정의된 것만 인식한다.
  • Instance에 동적으로 재정의해도 무시된다.

다음의 예는 instance-level overriding이 영향을 주지 못함을 보여줌.

class MyLen:
    def __len__(self):
        return 10

obj = MyLen()
print(len(obj))  # 10

obj.__len__ = lambda: 999  # instance에 동적 할당
print(obj.__len__())       # 999 (직접 호출하면 작동)
print(len(obj))            # 10 (len()은 여전히 Class의 __len__ 사용!)

이것은 매우 중요한 차이이다:

Special method는
Instance가 아닌
Type(Class)을 기준으로 dispatch된다.


5. 왜 Type을 기준으로 하는가?

Python이 special method를 Type 기준으로 dispatch하는 이유는 일관성과 예측 가능성 때문이다.

만약 instance마다 __len__을 다르게 정의할 수 있다면,

같은 Class의 object들이 len()에 대해 완전히 다르게 동작할 수 있다. 이는 코드의 예측 가능성을 해친다.

 

다음 구조를 기억하자:

Instance ─── 상태(데이터)를 가짐
    │
    ▼
Class ───── 동작(special method)을 정의함
    │
    ▼
Interpreter ─ Class에 정의된 special method를 dispatch함
  • Instance는 자신만의 데이터(self.value 등)를 가진다.
  • 하지만 연산자나 built-in 함수와의 상호작용 방식은 Class가 결정한다.

6. 모든 것은 Object이다

Python의 가장 중요한 특징 중 하나는 다음이다:

Python에서 모든 것은 Object이다.
Class조차도 Object이다.

class Dog:
    pass

print(type(Dog))  # <class 'type'>

 

Dog이라는 Class 자체도 하나의 object이며, 그 type은 type이다.


6.1 계층 구조

class Animal:
    pass

class Dog(Animal):
    pass

buddy = Dog()

print(type(buddy))  # <class '__main__.Dog'>
print(type(Dog))    # <class 'type'>
print(type(Animal)) # <class 'type'>
print(type(type))   # <class 'type'>

이 구조에서:

  • buddyDog의 instance
  • DogAnimal의 subclass이면서, type의 instance
  • type은 모든 class의 type이며, 자기 자신의 type이기도 함 (self-hosted)

6.2 왜 이것이 중요한가?

앞서 배운 규칙을 떠올려보자:

Special method는
Type을 기준으로 dispatch된다.

 

이 규칙은 Instance뿐만 아니라 Class에도 동일하게 적용된다:

  • Instance의 동작 → Instance의 Type인 Class가 정의
  • Class의 동작 → Class의 Type인 Metaclass가 정의
Instance의 special method dispatch ← Class가 담당
Class의 special method dispatch ← Metaclass가 담당

7. Metaclass: Class를 만드는 Class

7.1 Metaclass란?

Metaclass는 Class를 생성하는 Class이다.

 

일반적으로 우리는 Class를 정의하고, 그 Class로 instance를 만든다:

class Dog:      # class 정의
    pass

buddy = Dog()   # instance 생성

 

마찬가지로, Metaclass는 Class를 만든다:

# type이 Dog class를 만든다
Dog = type('Dog', (), {})

기본 Metaclass는 type이다.


7.2 class 문이 실행될 때 일어나는 일

class Dog:
    species = "Canis familiaris"

    def bark(self):
        return "Woof!"

 

이 코드가 실행되면, Python은 내부적으로 다음과 유사한 과정을 거친다:

  1. Class body를 실행하여 namespace(dict)를 생성: {"species": "Canis familiaris", "bark": <function>}
  2. Metaclass(기본값 type)를 호출: type("Dog", (), namespace)
    • 클래스 = type('클래스이름', base 클래스 튜플, 속성 namespace)
  3. Class object가 생성됨
# 위 class 정의와 동등한 코드
def bark(self):
    return "Woof!"

Dog = type("Dog", (), {"species": "Canis familiaris", "bark": bark})

7.3 Custom Metaclass 예제

Metaclass를 직접 정의하면 Class 생성 과정을 제어할 수 있다:

class AutoStr(type):
    """__str__이 없으면 자동으로 추가하는 metaclass"""

    def __new__(cls, name, bases, namespace):
        if "__str__" not in namespace:
            def __str__(self):
                return f"{name} instance"
            namespace["__str__"] = __str__
        return super().__new__(cls, name, bases, namespace)

class Person(metaclass=AutoStr):
    def __init__(self, name):
        self.name = name

p = Person("Alice")
print(p)  # "Person instance" (__str__이 자동 추가됨)

 

이 예제에서:

  • AutoStr metaclass가 Person class를 생성할 때
  • __str__이 정의되어 있지 않으면 자동으로 추가한다.

Metaclass는
Data Model(special method)이 적용될 구조 자체를
설계하는 상위 계층이다.


참고: Attribute Lookup의 이해

지금까지 special method의 dispatch 규칙을 살펴보았다. 이제 일반적인 attribute access가 어떻게 동작하는지 알아보자.

8.1 Instance Attribute Access

obj.attr을 실행하면 Python은 다음 과정을 거친다:

 

Step 1: __getattribute__ 호출

모든 attribute access는 type(obj).__getattribute__(obj, 'attr')로 시작한다.

 

Step 2: 기본 __getattribute__의 탐색 순서

(a) Type의 MRO에서 data descriptor 탐색
    → 찾으면: descriptor.__get__(obj, type(obj)) 반환

(b) Instance의 __dict__ 탐색
    → 찾으면: 해당 값 반환

(c) Type의 MRO에서 non-data descriptor 또는 class attribute 탐색
    → 찾으면: 해당 값 반환

(d) 모두 실패: AttributeError 발생

 

주의할 점은 일반 method는 non-data descriptor임!
때문에 (a)에서 탐색되지  못하며,
(b)가 먼저 있어서 instance-level overriding이 가능함.

 

Step 3: __getattr__ fallback

앞서에서 AttributeError가 발생할 경우 한정하여,

만약 __getattr__이 정의되어 있다면 이를 호출 (fallback).

class Example:
    x = 10  # class attribute

    def __getattr__(self, name):
        return f"'{name}' not found"

e = Example()
e.y = 20  # instance attribute

print(e.x)  # 10 (class attribute)
print(e.y)  # 20 (instance __dict__)
print(e.z)  # "'z' not found" (__getattr__ fallback)

 

앞서 instance에서 일반 method의 호출을 결정하는 attribute lookup 도 내부를 살펴보면 Type 이 관여함.

 

둘 다 궁극적으로 Class(instace 의 Type)에 정의된 method를 사용하지만:

  • 일반 method: obj.method() → Instance __dict__ 먼저 확인 → 없으면 Class에서 탐색
  • Special method: len(obj) → Instance __dict__ 건너뛰고 → type(obj)에서 바로 탐색

8.2 Descriptor란?

Descriptor는 __get__, __set__, __delete__ 중 하나 이상을 정의한 object이다.

  • Data descriptor: __get__과 함께 __set__ 또는 __delete__ 정의
  • Non-data descriptor: __get__만 정의. 일반 method가 여기에 속함.

핵심: Data descriptor는 instance __dict__보다 우선순위가 높다.

class DataDesc:
    def __get__(self, obj, objtype=None):
        return "from data descriptor"
    def __set__(self, obj, value):
        pass  # __set__ 정의 → data descriptor

class NonDataDesc:
    def __get__(self, obj, objtype=None):
        return "from non-data descriptor"

class MyClass:
    data = DataDesc()
    nondata = NonDataDesc()

obj = MyClass()
obj.__dict__['data'] = "from instance"
obj.__dict__['nondata'] = "from instance"

print(obj.data)     # "from data descriptor" (data descriptor 우선)
print(obj.nondata)  # "from instance" (instance __dict__ 우선)

 

앞서 애기한 일반 method는 non-descriptor에 속함. 다음 예제코드를 참고:

class MyClass:
    def method(self):        # non-data descriptor (function)
        return "from class"
    
    @property
    def prop(self):          # data descriptor
        return "from class"

obj = MyClass()
obj.__dict__['method'] = lambda: "from instance"
obj.__dict__['prop'] = "from instance"

print(obj.method())  # "from instance" (b에서 찾음)
print(obj.prop)      # "from class" (a에서 data descriptor가 우선)

8.3 Class Attribute Access

Class attribute access(Cls.attr)는 Metaclass가 관여한다.

 

앞서 배운 규칙이 동일하게 적용된다:

  • Instance attribute resolution → Type(Class)이 관여
  • Class attribute resolution → Type(Metaclass)이 관여
class Meta(type):
    @property
    def class_prop(cls):
        return "from metaclass"

class MyClass(metaclass=Meta):
    pass

print(MyClass.class_prop)  # "from metaclass"

9. 전체 구조 정리

Python Data Model의 핵심을 정리하면 다음과 같다:

9.1 Special Method Dispatch

Python Syntax (예: a + b, len(obj))
        │
        ▼
해당 Object의 Type에서 special method 탐색
        │
        ▼
Type에 정의된 special method 호출
  • Syntax는 special method로 변환되어 dispatch된다.
  • Dispatch는 Instance가 아닌 Type을 기준으로 한다.
  • Instance-level override는 무시된다.

9.2 객체 계층 구조

instance ──▶ class ──▶ metaclass
   │            │           │
   │            │           └─ class 생성 규칙 정의
   │            └─ instance의 special method 정의
   └─ 데이터(상태) 보유
  • Instance는 Class의 instance이다.
  • Class는 Metaclass의 instance이다.
  • Metaclass는 기본적으로 type이다.

9.3 일반 Method vs Special Method

구분 일반 Method Special Method
호출 주체 사용자 코드 Python Interpreter
Lookup 방식 Attribute lookup Type 기준 direct dispatch
Instance 재정의 가능 무시됨
예시 obj.calculate() len(obj), obj + other

 


요약

Python Data Model은 다음을 정의한다:

  1. Syntax와 Special Method의 매핑: +, len(), [] 같은 syntax가 어떤 special method로 변환되는지
  2. Dispatch 규칙: 해당 special method를 어디서(Type) 찾아 호출하는지
    • 좀 더 정확히 애기하면 일반 method의 dispatch도 Python Data Model에서 정의하고 있음.
    • 단, 일반 method는 attribute lookup 과정으로 dispatch 가 수행되고,
    • sepcial method는 __getattribute__ 를 우회한다는 차이가 있을 뿐임.
  3. 객체 계층 구조: Instance → Class → Metaclass로 이어지는 Type 중심 구조

이 규칙을 이해하면:

  • Custom class에서 연산자와 built-in 함수를 오버라이딩 할 수 있음.
  • Python이 "왜 그렇게 동작하는지"를 설명할 수 있음.
  • 고급 패턴(Descriptor, Metaclass)을 이해하고 활용할 수 있다.

Python Data Model은
Instance → Class → Metaclass로 이어지는 Type 중심 계층 위에서,
special method dispatch 규칙을 통해 객체의 동작을 정의하는 체계이다.


같이보면 좋은 자료들

2025.12.24 - [Python] - Python Class Definition and Object Model

 

Python Class Definition and Object Model

1. Object-Oriented Programming (OOP)에서의 Class 개념1.1 Class의 역할Class = State + Behavior Class는 Object-Oriented Programming에서 State와 Behavior를 함께 정의하는 추상화 단위임State는 Object가 보유하는 데이터의 집합

ds31x.tistory.com

열혈파이썬에서 해당 내용의 동영상 강좌:

https://player.vimeo.com/video/378703521

https://player.vimeo.com/video/378703708

https://player.vimeo.com/video/378703881

https://player.vimeo.com/video/378704294


 

728x90