메타클래스(Metaclass)
한 마디로 클래스를 만드는 클래스이며, 파이썬에서는 클래스도 객체입니다. 그럼 클래스를 만드는 객체는 무엇일까요? 바로 메타클래스입니다! 참고로 객체(Object)란 프로그래밍에서 데이터(속성, Attribute)와 그 데이터를 처리하는 동작(메서드, Method)을 함께 포함하는 하나의 독립적인 단위입니다.
클래스와 객체의 관계 다시 보기
파이썬에서 모든 객체는 어떤 클래스의 인스턴스입니다.
그런데 클래스도 객체입니다.
class Dog: pass d = Dog() print(type(d)) # <class '__main__.Dog'>
print(type(Dog)) # <class 'type'>
즉,
num = 10 # 정수 객체 text = "Hello" # 문자열 객체 lst = [1, 2, 3] # 리스트 객체 def func(): # 함수도 객체! return "Hi" class Dog: # 클래스도 객체! pass
여기서 인스턴스와 객체의 차이를 본다면,
객체(Object) → 프로그래밍에서 데이터와 기능을 함께 가진 모든 것 즉, 파이썬에서는 모든 것이 객체라고 볼 수 있습니다.
인스턴스(Instance) → 특정 클래스(Class)에서 생성된 객체
"모든 인스턴스는 객체이지만, 모든 객체가 인스턴스는 아니다!"num = 10 text = "Hello" lst = [1, 2, 3]
num, text, lst도 객체(Object)입니다.
하지만 우리가 만든 클래스(Dog)에서 생성된 것이 아니므로 "인스턴스"는 아닙니다.class Dog: pass d = Dog() print(type(d)) # <class '__main__.Dog'>
d는 Dog 클래스의 인스턴스입니다.
즉, Dog는 객체를 만들기 위한 틀(클래스) 역할을 합니다.
일단 본론으로 들어와서 type()이 메타클래스인것 아시나요?
type()이 "타입(클래스)을 확인하는 함수"라고 알고 있었는데,
type()은 두 가지 역할을 한다! 파이썬에서 type()은 오버로딩된 함수처럼 두 가지 역할을 합니다.
type(obj) → 객체의 클래스(타입)를 반환 가장 익숙한 사용법은 객체의 타입(클래스)을 확인하는 것입니다.
print(type(10)) # <class 'int'> print(type("hello")) # <class 'str'> print(type([])) # <class 'list'>
type(obj)는 객체의 클래스(타입)를 반환합니다.
여기서는 우리가 흔히 아는 "타입 확인 함수"로 동작합니다.
type(name, bases, dict) → 새로운 클래스를 생성 type()은 클래스를 동적으로 생성하는 메타클래스 역할도 합니다.
MyClass = type("MyClass", (object,), {"x": 10}) print(MyClass) # <class '__main__.MyClass'> print(MyClass.x) # 10
type(name, bases, dict)에서 bases와 dict란?
MyClass = type("MyClass", (object,), {"x": 10}) "MyClass" → 클래스 이름 (object,) → 부모 클래스(상속할 클래스) {"x": 10} → 클래스 속성(딕셔너리 형태로 정의)
bases → 부모 클래스(상속 관계)
bases는 튜플 형태로 클래스를 상속받을 부모 클래스를 지정합니다.즉, 어떤 클래스를 상속할지를 결정합니다.
class Parent: pass Child = type("Child", (Parent,), {}) # Parent를 상속 print(issubclass(Child, Parent)) # True
Child는 Parent를 상속받았으므로 issubclass(Child, Parent)가 True를 반환합니다.
bases 없이 만들면?
bases에 아무것도 지정하지 않으면 상속이 안 되므로 에러가 납니다.
bases는 반드시 튜플로 감싸야 합니다.# type("MyClass", {}, {}) # TypeError: bases must be tuple
만약 상속할 부모 클래스가 없으면 (object,)를 넣어 기본 클래스로 설정합니다.
dict → 클래스 속성과 메서드 정의
dict는 클래스 내부에 들어갈 속성(attribute)과 메서드(method)를 정의하는 딕셔너리입니다.
MyClass = type("MyClass", (object,), {"x": 10, "greet": lambda self: "안녕!"}) obj = MyClass() print(obj.x) # 10 print(obj.greet()) # "안녕!"
클래스 내부에 x = 10 속성과 greet() 메서드를 정의했습니다.
lambda self: "안녕!"은 메서드를 나타냅니다.
dict 없이 만들면?
dict를 {}로 비워두면 속성이 없는 빈 클래스가 됩니다.EmptyClass = type("EmptyClass", (object,), {}) print(dir(EmptyClass)) # 속성이 거의 없음
# (1) 일반적인 클래스 정의 class MyClass: x = 10 def greet(self): return "안녕!" # (2) type()을 사용해 같은 클래스 만들기 MyClass = type("MyClass", (object,), {"x": 10, "greet": lambda self: "안녕!"})
두 코드의 결과는 동일합니다.
type()을 사용하면 동적으로 클래스를 생성할 수 있음을 알 수 있습니다.
결국, 메타클래스는 "클래스를 만드는 클래스"입니다.
즉, 메타클래스는 클래스의 생성 과정을 제어할 수 있는 특별한 클래스입니다.
우리가 일반적으로 클래스를 정의할 때 사용하는 class는 메타클래스에 의해 만들어집니다.
type은 기본적인 메타클래스로, 우리가 클래스를 정의할 때 사용되는 기본 메타클래스 역할을 합니다.
일단 이렇게만 해놓고 자세한건 내일..