forked from ethan-funny/explore-python
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmetaclass.md
More file actions
325 lines (237 loc) · 9.98 KB
/
Copy pathmetaclass.md
File metadata and controls
325 lines (237 loc) · 9.98 KB
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# 陌生的 metaclass
Python 中的**元类(metaclass)**是一个深度魔法,平时我们可能比较少接触到元类,本文将通过一些简单的例子来理解这个魔法。
## 类也是对象
在 Python 中,一切皆对象。字符串,列表,字典,函数是对象,**类也是一个对象**,因此你可以:
- 把类赋值给一个变量
- 把类作为函数参数进行传递
- 把类作为函数的返回值
- 在运行时动态地创建类
看一个简单的例子:
```python
class Foo(object):
foo = True
class Bar(object):
bar = True
def echo(cls):
print cls
def select(name):
if name == 'foo':
return Foo # 返回值是一个类
if name == 'bar':
return Bar
>>> echo(Foo) # 把类作为参数传递给函数 echo
<class '__main__.Foo'>
>>> cls = select('foo') # 函数 select 的返回值是一个类,把它赋给变量 cls
>>> cls
__main__.Foo
```
## 熟悉又陌生的 type
在日常使用中,我们经常使用 `object` 来派生一个类,事实上,在这种情况下,Python 解释器会调用 `type` 来创建类。
这里,出现了 `type`,没错,是你知道的 `type`,我们经常使用它来判断一个对象的类型,比如:
```python
class Foo(object):
Foo = True
>>> type(10)
<type 'int'>
>>> type('hello')
<type 'str'>
>>> type(Foo())
<class '__main__.Foo'>
>>> type(Foo)
<type 'type'>
```
**事实上,`type` 除了可以返回对象的类型,它还可以被用来动态地创建类(对象)**。下面,我们看几个例子,来消化一下这句话。
使用 `type` 来创建类(对象)的方式如下:
> type(类名, 父类的元组(针对继承的情况,可以为空),包含属性和方法的字典(名称和值))
### 最简单的情况
假设有下面的类:
```python
class Foo(object):
pass
```
现在,我们不使用 `class` 关键字来定义,而使用 `type`,如下:
```python
Foo = type('Foo', (object, ), {}) # 使用 type 创建了一个类对象
```
上面两种方式是等价的。我们看到,`type` 接收三个参数:
- 第 1 个参数是字符串 'Foo',表示类名
- 第 2 个参数是元组 (object, ),表示所有的父类
- 第 3 个参数是字典,这里是一个空字典,表示没有定义属性和方法
在上面,我们使用 `type()` 创建了一个名为 Foo 的类,然后把它赋给了变量 Foo,我们当然可以把它赋给其他变量,但是,此刻没必要给自己找麻烦。
接着,我们看看使用:
```python
>>> print Foo
<class '__main__.Foo'>
>>> print Foo()
<__main__.Foo object at 0x10c34f250>
```
### 有属性和方法的情况
假设有下面的类:
```python
class Foo(object):
foo = True
def greet(self):
print 'hello world'
print self.foo
```
用 `type` 来创建这个类,如下:
```python
def greet(self):
print 'hello world'
print self.foo
Foo = type('Foo', (object, ), {'foo': True, 'greet': greet})
```
上面两种方式的效果是一样的,看下使用:
```python
>>> f = Foo()
>>> f.foo
True
>>> f.greet
<bound method Foo.greet of <__main__.Foo object at 0x10c34f890>>
>>> f.greet()
hello world
True
```
### 继承的情况
再来看看继承的情况,假设有如下的父类:
```python
class Base(object):
pass
```
我们用 Base 派生一个 Foo 类,如下:
```python
class Foo(Base):
foo = True
```
改用 `type` 来创建,如下:
```python
Foo = type('Foo', (Base, ), {'foo': True})
```
## 什么是元类(metaclass)
**元类(metaclass)是用来创建类(对象)的可调用对象。**这里的可调用对象可以是函数或者类等。但一般情况下,我们使用类作为元类。对于实例对象、类和元类,我们可以用下面的图来描述:
```python
类是实例对象的模板,元类是类的模板
+----------+ +----------+ +----------+
| | | | | |
| | instance of | | instance of | |
| instance +------------>+ class +------------>+ metaclass|
| | | | | |
| | | | | |
+----------+ +----------+ +----------+
```
我们在前面使用了 `type` 来创建类(对象),事实上,**`type` 就是一个元类**。
那么,元类到底有什么用呢?要你何用...
**元类的主要目的是为了控制类的创建行为。**我们还是先来看看一些例子,以消化这句话。
## 元类的使用
先从一个简单的例子开始,假设有下面的类:
```python
class Foo(object):
name = 'foo'
def bar(self):
print 'bar'
```
现在我们想给这个类的方法和属性名称前面加上 `my_` 前缀,即 name 变成 my_name,bar 变成 my_bar,另外,我们还想加一个 echo 方法。当然,有很多种做法,这里展示用元类的做法。
1.首先,定义一个元类,按照默认习惯,类名以 Metaclass 结尾,代码如下:
```python
class PrefixMetaclass(type):
def __new__(cls, name, bases, attrs):
# 给所有属性和方法前面加上前缀 my_
_attrs = (('my_' + name, value) for name, value in attrs.items())
_attrs = dict((name, value) for name, value in _attrs) # 转化为字典
_attrs['echo'] = lambda self, phrase: phrase # 增加了一个 echo 方法
return type.__new__(cls, name, bases, _attrs) # 返回创建后的类
```
上面的代码有几个需要注意的点:
- PrefixMetaClass 从 `type` 继承,这是因为 PrefixMetaclass 是用来创建类的
- `__new__` 是在 `__init__` 之前被调用的特殊方法,它用来创建对象并返回创建后的对象,对它的参数解释如下:
- cls:当前准备创建的类
- name:类的名字
- bases:类的父类集合
- attrs:类的属性和方法,是一个字典
2.接着,我们需要指示 Foo 使用 PrefixMetaclass 来定制类。
在 Python2 中,我们只需在 Foo 中加一个 `__metaclass__` 的属性,如下:
```python
class Foo(object):
__metaclass__ = PrefixMetaclass
name = 'foo'
def bar(self):
print 'bar'
```
在 Python3 中,这样做:
```python
class Foo(metaclass=PrefixMetaclass):
name = 'foo'
def bar(self):
print 'bar'
```
现在,让我们看看使用:
```python
>>> f = Foo()
>>> f.name # name 属性已经被改变
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-774-4511c8475833> in <module>()
----> 1 f.name
AttributeError: 'Foo' object has no attribute 'name'
>>>
>>> f.my_name
'foo'
>>> f.my_bar()
bar
>>> f.echo('hello')
'hello'
```
可以看到,Foo 原来的属性 name 已经变成了 my_name,而方法 bar 也变成了 my_bar,这就是元类的魔法。
再来看一个继承的例子,下面是完整的代码:
```python
class PrefixMetaclass(type):
def __new__(cls, name, bases, attrs):
# 给所有属性和方法前面加上前缀 my_
_attrs = (('my_' + name, value) for name, value in attrs.items())
_attrs = dict((name, value) for name, value in _attrs) # 转化为字典
_attrs['echo'] = lambda self, phrase: phrase # 增加了一个 echo 方法
return type.__new__(cls, name, bases, _attrs)
class Foo(object):
__metaclass__ = PrefixMetaclass # 注意跟 Python3 的写法有所区别
name = 'foo'
def bar(self):
print 'bar'
class Bar(Foo):
prop = 'bar'
```
其中,PrefixMetaclass 和 Foo 跟前面的定义是一样的,只是新增了 Bar,它继承自 Foo。先让我们看看使用:
```python
>>> b = Bar()
>>> b.prop # 发现没这个属性
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-778-825e0b6563ea> in <module>()
----> 1 b.prop
AttributeError: 'Bar' object has no attribute 'prop'
>>> b.my_prop
'bar'
>>> b.my_name
'foo'
>>> b.my_bar()
bar
>>> b.echo('hello')
'hello'
```
我们发现,Bar 没有 prop 这个属性,但是有 my_prop 这个属性,这是为什么呢?
原来,当我们定义 `class Bar(Foo)` 时,Python 会首先在当前类,即 Bar 中寻找 `__metaclass__`,如果没有找到,就会在父类 Foo 中寻找 `__metaclass__`,如果找不到,就继续在 Foo 的父类寻找,如此继续下去,如果在任何父类都找不到 `__metaclass__`,就会到模块层次中寻找,如果还是找不到,就会用 type 来创建这个类。
这里,我们在 Foo 找到了 `__metaclass__`,Python 会使用 PrefixMetaclass 来创建 Bar,也就是说,元类会隐式地继承到子类,虽然没有显示地在子类使用 `__metaclass__`,这也解释了为什么 Bar 的 prop 属性被动态修改成了 my_prop。
写到这里,不知道你理解元类了没?希望理解了,如果没理解,就多看几遍吧~
# 小结
- 在 Python 中,类也是一个对象。
- 类创建实例,元类创建类。
- 元类主要做了三件事:
- 拦截类的创建
- 修改类的定义
- 返回修改后的类
- 当你创建类时,解释器会调用元类来生成它,定义一个继承自 object 的普通类意味着调用 type 来创建它。
# 参考资料
- [oop - What is a metaclass in Python? - Stack Overflow](http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python)
- [深刻理解Python中的元类(metaclass) - 伯乐在线](http://blog.jobbole.com/21351/)
- [使用元类 - 廖雪峰的官方网站](http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014319106919344c4ef8b1e04c48778bb45796e0335839000)
- [Python基础:元类](http://www.cnblogs.com/russellluo/p/3409602.html)
- [在Python中使用class decorator和metaclass](http://blog.zhangyu.so/python/2016/02/19/class-decorator-and-metaclass-in-python/)