Python Class Creation
class
키워드를 이용하여 클래스를 정의할 때, class object가 생성되는 걸 우리는 안다.
그 과정은 instance를 생성하는 것보다 훨씬 복잡하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
class Member:
def __repr__(self) -> str:
return "<'Member' object>"
def __set_name__(self, cls, name):
"""
Set Name (instance) method of the class attribute is called while creating raw class object.
:param `self`: `Member` instance which will be a class attribute
:param `cls`: Raw class object whose class attribute is `self`
:param `name`: Class attribute name of `self`
"""
print('Member.__set_name__() is called.')
print('Member.__set_name__() is called while creating raw class object.')
print(f'Sets class attribute {name} of the class objct {cls}')
class Type(type):
"""Mocks `type` class."""
def __repr__(self) -> str:
"""Print name of `Type`'s instance (self)."""
return f"<class '{self.__name__}'>"
def __prepare__(name, bases, **kwds):
"""
Prepare (static)method of the metaclass is called to create namespace.
By default, `type.__prepare__(name, bases)` is called.
:param `name`: name of the class to create
:param `bases`: bases of the class to create
:param `**kwds`: provided keywords of the class to create
"""
print('Type.__prepare__() is called.')
print('Type.__prepare__() is called before creating a class whose metaclass is `Type`.')
print(f'Creates namespace for the class {name} with bases {bases}, kwds {kwds}.')
namespace = type.__prepare__(name, bases, **kwds)
print(f'Created namespace {namespace} will be used while executing the body of class {name}.', end='\n\n')
return namespace
def __new__(mcs, name, bases, namespace, **kwds):
"""
New (class)method of the metaclass is called to create raw class object.
By default, `type.__new__(mcs, name, bases, namespace, **kwds)` is called.
:param `mcs`: metaclass, which is `Type` by default
:param `name`: name of the class to create
:param `bases`: bases of the class to create
:param `namespace`: namespace used to create the class
:param `**kwds`: provided keywords of the class to create
"""
print('Type.__new__() is called.')
print('Type.__new__() is called to create a class object whose metaclass is `Type`.')
print(f'Creates class {name} of metaclass {mcs} with bases {bases}, kwds {kwds} using namespace {namespace}.')
obj = type.__new__(mcs, name, bases, namespace, **kwds)
print(f'Created class {obj} is an instance of `Type`.', end='\n\n')
return obj
def __init__(cls, name, bases, namespace, **kwds):
"""
Init (instance) method of the metaclass is called to initialize class object.
By default, `type.__init__(cls, name, bases, namespace, **kwds) is called.
:param `cls`: class object to initialize
:param `name`: name of the class to initialize
:param `bases`: bases of the class to initialize
:param `namespace`: namespace used while creating the class
:param `**kwds`: provided keywords of the class on create
"""
print('Type.__init__() is called.')
print('Type.__init__() is called to initialize a class object whose metaclass is `Type`.')
print(f'Initializes class object {cls} whose name is {name} with bases {bases}, kwds {kwds} using namespace {namespace}.', end='\n\n')
type.__init__(cls, name, bases, namespace, **kwds)
def __call__(cls, *args, **kwds):
"""
Call (instance) method of the metaclass is called when calling class object.
By default, `type.__call__(cls, *args, **kwds) is called, which usually creates an instance of the class.
:param `cls`: class object that is called
:param `*args`: positional argument provided when calling class object
:param `**kwds`: keyword argument provided when calling class object
"""
print('Type.__call__() is called.')
print('Type.__call__() is called when calling class object whose metaclass is Type.')
print('Type.__call__() is usually called to create an instance of the class.')
print(f'Calls class object {cls} with args {args} and kwds {kwds}.')
instance = type.__call__(cls, *args, **kwds)
print(f'Created instance {instance}', end='\n\n')
return instance
print('=========Create class `Parent`=========', end='\n\n')
class Parent(metaclass=Type):
print(f"Executes body of `Parent` with namespace provided by Type.__prepare__().")
def __repr__(self) -> str:
return "<'Parent' object>"
def __init_subclass__(cls, **kwds) -> None:
"""
Init Subclass (class)method of the base class is called before calling its own init method.
:param `cls`: class object to initialize
:param `**kwds`: provided keywords of the class on create
"""
print('Parent.__init_subclass__() is called.')
print('Parent.__init_subclass__() is called to initialize a class object whose base is `Parent`.')
print(f'Initializes class object {cls} whose base is `Parent` with kwds {kwds}')
print(f"Added '__module__', '__qualname__', '__repr__' and '__init_subclass__' to the namespace.", end='\n\n')
class WrongParent:
def __repr__(self) -> str:
return "<'WrongParent' object>"
def __mro_entries__(self, bases):
"""
MRO Entries (instance) method of the base is called during MRO resolution of the class to create.
:param `self`: `WrongParent` object included in original `bases`.
:param `bases`: Bases of the class to create.
"""
print('WrongParent.__mro_entries__() is called.')
print('WrongParent.__mro_entries__() is called during MRO resolution of the class to create.')
print(f'Resolves MRO entry of {self} among bases {bases}.')
mro = (WrongParent,)
print(f'Replaced {self} into {mro}.', end='\n\n')
return mro
print('=========Create class `Class`=========', end='\n\n')
class Class(WrongParent(), Parent, member=Member()):
print(f"Executes body of `Class` with namespace provided by Type.__prepare__().")
x = Member()
def __repr__(self) -> str:
return "<'Class' object>"
print(f"Added '__module__', '__qualname__', 'x', '__repr__' and '__orig_bases__' to the namespace.", end='\n\n')
Mechanism
- 클래스의 base들 중에서
type
의 instance가 아닌데__mro_entries__(self, bases)
메소드가 있는 object이 있다면 해당 object를__mro_entries__
를 호출한 결과를 unpack하여 대체한다.self
는 문제가 된 object이고bases
는 주어진 클래스의 base들.__mro_entries__
는 반드시 tuple을 return해야 한다. - 정해진 MRO를 바탕으로 class의 (type)metaclass를 결정한다.
- Base가 없고 metaclass를 명시하지 않았다면 default로
type
이다. type
의 instance가 아닌 metaclass를 명시했다면 그대로 채용한다.- 명시된 metaclass와 base들의 metaclass들 전부의 subtype인 type을 채용한다.
- 그런 type이 없으면
TypeError
로 class를 생성에 실패한다.
- Base가 없고 metaclass를 명시하지 않았다면 default로
- 위에서 정해진 metaclass의
__prepare__(name, bases, **kwds)
를 호출해서 namespace를 생성한다.name
은 만들 클래스의 이름,bases
와kwds
는 클래스 정의(괄호 안)에 있는 것들이다. 단, kwds에서metaclass=
인 argument들은 전부 제외시킨다. - 만들어진 namespace에서 class body를 실행시킨다. 그 결과, namespace에 method의 원형 함수 등 class attribute들이 추가된다.
- metaclass의
__new__(mcs, name, bases, namespace, **kwds)
를 호출하여 클래스를 생성하고__class__
,__mro__
등 기본적인 것들을 셋팅한다.mcs
는 metaclass,name
은 만들 클래스의 이름,bases
와kwds
는 클래스 정의(괄호 안)의 것들, 그리고namespace
는 4의 결과이다. 이 때 하는 일은 먼저 namespace를 보고 class attribute를 setting하는데, 이 때 mapping되는 object의__set_name__(self, cls, name)
이 호출된다.self
는 attribute object,cls
는 class object, 그리고name
은 attribute name이다. 그 다음은 각 base들에서__init_subclass__(cls, **kwds)
를 호출한다.cls
는 class object,kwds
는 클래스 정의(괄호 안)에 있는 keyword argument들이다. - metaclass의
__init__
을 호출하여 객체를 초기화한다.
Parent
Class
- Base가 없으므로 skip
- 2-3에서 Metaclass =
Type
으로 결정 - Namespace =
{}
- Namespace =
{'__repr__': ..., '__init_subclass__': ..., ...}
- obj =
Type
의 instance, whose name isParent
=========Create class `Parent`=========
Type.__prepare__() is called.
Type.__prepare__() is called before creating a class whose metaclass is `Type`.
Creates namespace for the class Parent with bases (), kwds {}.
Created namespace {} will be used while executing the body of class Parent.
Executes body of `Parent` with namespace provided by Type.__prepare__().
Added '__module__', '__qualname__', '__repr__' and '__init_subclass__' to the namespace.
Type.__new__() is called.
Type.__new__() is called to create a class object whose metaclass is `Type`.
Creates class Parent of metaclass <class '__main__.Type'> with bases (), kwds {} using namespace {'__module__': '__main__', '__qualname__': 'Parent', '__repr__': <function Parent.__repr__ at 0x1027da7a0>, '__init_subclass__': <function Parent.__init_subclass__ at 0x1027da840>}.
Created class <class 'Parent'> is an instance of `Type`.
Type.__init__() is called.
Type.__init__() is called to initialize a class object whose metaclass is `Type`.
Initializes class object <class 'Parent'> whose name is Parent with bases (), kwds {} using namespace {'__module__': '__main__', '__qualname__': 'Parent', '__repr__': <function Parent.__repr__ at 0x1027da7a0>, '__init_subclass__': <function Parent.__init_subclass__ at 0x1027da840>}.
Class
Class
WrongParent()
객체는type
의 instance가 아니므로__mro_entries__
호출하여WrongParent
클래스로 base가 바뀜- 2-3에서 Metaclass =
Type
으로 결정 - Namespace =
{member: Member object}
- Namespace =
{member: Member object}
- obj =
Type
의 instance, whose name isClass
+ Member에서__set_name__
, Parent에서__init_subclass__
호출됨
=========Create class `Class`=========
WrongParent.__mro_entries__() is called.
WrongParent.__mro_entries__() is called during MRO resolution of the class to create.
Resolves MRO entry of <'WrongParent' object> among bases (<'WrongParent' object>, <class 'Parent'>).
Replaced <'WrongParent' object> into (<class '__main__.WrongParent'>,).
Type.__prepare__() is called.
Type.__prepare__() is called before creating a class whose metaclass is `Type`.
Creates namespace for the class Class with bases (<class '__main__.WrongParent'>, <class 'Parent'>), kwds {'member': <'Member' object>}.
Created namespace {} will be used while executing the body of class Class.
Executes body of `Class` with namespace provided by Type.__prepare__().
Added '__module__', '__qualname__', 'x', '__repr__' and '__orig_bases__' to the namespace.
Type.__new__() is called.
Type.__new__() is called to create a class object whose metaclass is `Type`.
Creates class Class of metaclass <class '__main__.Type'> with bases (<class '__main__.WrongParent'>, <class 'Parent'>), kwds {'member': <'Member' object>} using namespace {'__module__': '__main__', '__qualname__': 'Class', 'x': <'Member' object>, '__repr__': <function Class.__repr__ at 0x1027daa20>, '__orig_bases__': (<'WrongParent' object>, <class 'Parent'>)}.
Member.__set_name__() is called.
Member.__set_name__() is called while creating raw class object.
Sets class attribute x of the class objct <class 'Class'>
Parent.__init_subclass__() is called.
Parent.__init_subclass__() is called to initialize a class object whose base is `Parent`.
Initializes class object <class 'Class'> whose base is `Parent` with kwds {'member': <'Member' object>}
Created class <class 'Class'> is an instance of `Type`.
Type.__init__() is called.
Type.__init__() is called to initialize a class object whose metaclass is `Type`.
Initializes class object <class 'Class'> whose name is Class with bases (<class '__main__.WrongParent'>, <class 'Parent'>), kwds {'member': <'Member' object>} using namespace {'__module__': '__main__', '__qualname__': 'Class', 'x': <'Member' object>, '__repr__': <function Class.__repr__ at 0x1027daa20>, '__orig_bases__': (<'WrongParent' object>, <class 'Parent'>)}.