Skip to content

Commit ddcc422

Browse files
committed
add metaclass
1 parent 58b99b1 commit ddcc422

1 file changed

Lines changed: 324 additions & 1 deletion

File tree

Class/metaclass.md

Lines changed: 324 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,325 @@
1-
# 元类
1+
# 陌生的 metaclass
2+
3+
Python 中的**元类(metaclass)**是一个深度魔法,平时我们可能比较少接触到元类,本文将通过一些简单的例子来理解这个魔法。
4+
5+
## 类也是对象
6+
7+
在 Python 中,一切皆对象。字符串,列表,字典,函数是对象,**类也是一个对象**,因此你可以:
8+
9+
- 把类赋值给一个变量
10+
- 把类作为函数参数进行传递
11+
- 把类作为函数的返回值
12+
- 在运行时动态地创建类
13+
14+
看一个简单的例子:
15+
16+
```python
17+
class Foo(object):
18+
foo = True
19+
20+
class Bar(object):
21+
bar = True
22+
23+
def echo(cls):
24+
print cls
25+
26+
def select(name):
27+
if name == 'foo':
28+
return Foo # 返回值是一个类
29+
if name == 'bar':
30+
return Bar
31+
32+
>>> echo(Foo) # 把类作为参数传递给函数 echo
33+
<class '__main__.Foo'>
34+
>>> cls = select('foo') # 函数 select 的返回值是一个类,把它赋给变量 cls
35+
>>> cls
36+
__main__.Foo
37+
```
38+
39+
## 熟悉又陌生的 type
40+
41+
在日常使用中,我们经常使用 `object` 来派生一个类,事实上,在这种情况下,Python 解释器会调用 `type` 来创建类。
42+
43+
这里,出现了 `type`,没错,是你知道的 `type`,我们经常使用它来判断一个对象的类型,比如:
44+
45+
```python
46+
class Foo(object):
47+
Foo = True
48+
49+
>>> type(10)
50+
<type 'int'>
51+
>>> type('hello')
52+
<type 'str'>
53+
>>> type(Foo())
54+
<class '__main__.Foo'>
55+
>>> type(Foo)
56+
<type 'type'>
57+
```
58+
59+
**事实上,`type` 除了可以返回对象的类型,它还可以被用来动态地创建类(对象)**。下面,我们看几个例子,来消化一下这句话。
60+
61+
使用 `type` 来创建类(对象)的方式如下:
62+
63+
> type(类名, 父类的元组(针对继承的情况,可以为空),包含属性和方法的字典(名称和值))
64+
65+
### 最简单的情况
66+
67+
假设有下面的类:
68+
69+
```python
70+
class Foo(object):
71+
pass
72+
```
73+
74+
现在,我们不使用 `class` 关键字来定义,而使用 `type`,如下:
75+
76+
```python
77+
Foo = type('Foo', (object, ), {}) # 使用 type 创建了一个类对象
78+
```
79+
80+
上面两种方式是等价的。我们看到,`type` 接收三个参数:
81+
82+
- 第 1 个参数是字符串 'Foo',表示类名
83+
- 第 2 个参数是元组 (object, ),表示所有的父类
84+
- 第 3 个参数是字典,这里是一个空字典,表示没有定义属性和方法
85+
86+
在上面,我们使用 `type()` 创建了一个名为 Foo 的类,然后把它赋给了变量 Foo,我们当然可以把它赋给其他变量,但是,此刻没必要给自己找麻烦。
87+
88+
接着,我们看看使用:
89+
90+
```python
91+
>>> print Foo
92+
<class '__main__.Foo'>
93+
>>> print Foo()
94+
<__main__.Foo object at 0x10c34f250>
95+
```
96+
97+
### 有属性和方法的情况
98+
99+
假设有下面的类:
100+
101+
```python
102+
class Foo(object):
103+
foo = True
104+
def greet(self):
105+
print 'hello world'
106+
print self.foo
107+
```
108+
109+
`type` 来创建这个类,如下:
110+
111+
```python
112+
def greet(self):
113+
print 'hello world'
114+
print self.foo
115+
116+
Foo = type('Foo', (object, ), {'foo': True, 'greet': greet})
117+
```
118+
119+
上面两种方式的效果是一样的,看下使用:
120+
121+
```python
122+
>>> f = Foo()
123+
>>> f.foo
124+
True
125+
>>> f.greet
126+
<bound method Foo.greet of <__main__.Foo object at 0x10c34f890>>
127+
>>> f.greet()
128+
hello world
129+
True
130+
```
131+
132+
### 继承的情况
133+
134+
再来看看继承的情况,假设有如下的父类:
135+
136+
```python
137+
class Base(object):
138+
pass
139+
```
140+
141+
我们用 Base 派生一个 Foo 类,如下:
142+
143+
```python
144+
class Foo(Base):
145+
foo = True
146+
```
147+
148+
改用 `type` 来创建,如下:
149+
150+
```python
151+
Foo = type('Foo', (Base, ), {'foo': True})
152+
```
153+
154+
## 什么是元类(metaclass)
155+
156+
**元类(metaclass)是用来创建类(对象)的可调用对象。**这里的可调用对象可以是函数或者类等。但一般情况下,我们使用类作为元类。对于实例对象、类和元类,我们可以用下面的图来描述:
157+
158+
```python
159+
类是实例对象的模板,元类是类的模板
160+
161+
+----------+ +----------+ +----------+
162+
| | | | | |
163+
| | instance of | | instance of | |
164+
| instance +------------>+ class +------------>+ metaclass|
165+
| | | | | |
166+
| | | | | |
167+
+----------+ +----------+ +----------+
168+
```
169+
170+
我们在前面使用了 `type` 来创建类(对象),事实上,**`type` 就是一个元类**
171+
172+
那么,元类到底有什么用呢?要你何用...
173+
174+
**元类的主要目的是为了控制类的创建行为。**我们还是先来看看一些例子,以消化这句话。
175+
176+
## 元类的使用
177+
178+
先从一个简单的例子开始,假设有下面的类:
179+
180+
```python
181+
class Foo(object):
182+
name = 'foo'
183+
def bar(self):
184+
print 'bar'
185+
```
186+
187+
现在我们想给这个类的方法和属性名称前面加上 `my_` 前缀,即 name 变成 my_name,bar 变成 my_bar,另外,我们还想加一个 echo 方法。当然,有很多种做法,这里展示用元类的做法。
188+
189+
1.首先,定义一个元类,按照默认习惯,类名以 Metaclass 结尾,代码如下:
190+
191+
```python
192+
class PrefixMetaclass(type):
193+
def __new__(cls, name, bases, attrs):
194+
# 给所有属性和方法前面加上前缀 my_
195+
_attrs = (('my_' + name, value) for name, value in attrs.items())
196+
197+
_attrs = dict((name, value) for name, value in _attrs) # 转化为字典
198+
_attrs['echo'] = lambda self, phrase: phrase # 增加了一个 echo 方法
199+
200+
return type.__new__(cls, name, bases, _attrs) # 返回创建后的类
201+
```
202+
203+
上面的代码有几个需要注意的点:
204+
205+
- PrefixMetaClass 从 `type` 继承,这是因为 PrefixMetaclass 是用来创建类的
206+
- `__new__` 是在 `__init__` 之前被调用的特殊方法,它用来创建对象并返回创建后的对象,对它的参数解释如下:
207+
- cls:当前准备创建的类
208+
- name:类的名字
209+
- bases:类的父类集合
210+
- attrs:类的属性和方法,是一个字典
211+
212+
2.接着,我们需要指示 Foo 使用 PrefixMetaclass 来定制类。
213+
214+
在 Python2 中,我们只需在 Foo 中加一个 `__metaclass__` 的属性,如下:
215+
216+
```python
217+
class Foo(object):
218+
__metaclass__ = PrefixMetaclass
219+
name = 'foo'
220+
def bar(self):
221+
print 'bar'
222+
```
223+
224+
在 Python3 中,这样做:
225+
226+
```python
227+
class Foo(metaclass=PrefixMetaclass):
228+
name = 'foo'
229+
def bar(self):
230+
print 'bar'
231+
```
232+
233+
现在,让我们看看使用:
234+
235+
```python
236+
>>> f = Foo()
237+
>>> f.name # name 属性已经被改变
238+
---------------------------------------------------------------------------
239+
AttributeError Traceback (most recent call last)
240+
<ipython-input-774-4511c8475833> in <module>()
241+
----> 1 f.name
242+
243+
AttributeError: 'Foo' object has no attribute 'name'
244+
>>>
245+
>>> f.my_name
246+
'foo'
247+
>>> f.my_bar()
248+
bar
249+
>>> f.echo('hello')
250+
'hello'
251+
```
252+
253+
可以看到,Foo 原来的属性 name 已经变成了 my_name,而方法 bar 也变成了 my_bar,这就是元类的魔法。
254+
255+
再来看一个继承的例子,下面是完整的代码:
256+
257+
```python
258+
class PrefixMetaclass(type):
259+
def __new__(cls, name, bases, attrs):
260+
# 给所有属性和方法前面加上前缀 my_
261+
_attrs = (('my_' + name, value) for name, value in attrs.items())
262+
263+
_attrs = dict((name, value) for name, value in _attrs) # 转化为字典
264+
_attrs['echo'] = lambda self, phrase: phrase # 增加了一个 echo 方法
265+
266+
return type.__new__(cls, name, bases, _attrs)
267+
268+
class Foo(object):
269+
__metaclass__ = PrefixMetaclass # 注意跟 Python3 的写法有所区别
270+
name = 'foo'
271+
def bar(self):
272+
print 'bar'
273+
274+
class Bar(Foo):
275+
prop = 'bar'
276+
```
277+
278+
其中,PrefixMetaclass 和 Foo 跟前面的定义是一样的,只是新增了 Bar,它继承自 Foo。先让我们看看使用:
279+
280+
```python
281+
>>> b = Bar()
282+
>>> b.prop # 发现没这个属性
283+
---------------------------------------------------------------------------
284+
AttributeError Traceback (most recent call last)
285+
<ipython-input-778-825e0b6563ea> in <module>()
286+
----> 1 b.prop
287+
288+
AttributeError: 'Bar' object has no attribute 'prop'
289+
>>> b.my_prop
290+
'bar'
291+
>>> b.my_name
292+
'foo'
293+
>>> b.my_bar()
294+
bar
295+
>>> b.echo('hello')
296+
'hello'
297+
```
298+
299+
我们发现,Bar 没有 prop 这个属性,但是有 my_prop 这个属性,这是为什么呢?
300+
301+
原来,当我们定义 `class Bar(Foo)` 时,Python 会首先在当前类,即 Bar 中寻找 `__metaclass__`,如果没有找到,就会在父类 Foo 中寻找 `__metaclass__`,如果找不到,就继续在 Foo 的父类寻找,如此继续下去,如果在任何父类都找不到 `__metaclass__`,就会到模块层次中寻找,如果还是找不到,就会用 type 来创建这个类。
302+
303+
这里,我们在 Foo 找到了 `__metaclass__`,Python 会使用 PrefixMetaclass 来创建 Bar,也就是说,元类会隐式地继承到子类,虽然没有显示地在子类使用 `__metaclass__`,这也解释了为什么 Bar 的 prop 属性被动态修改成了 my_prop。
304+
305+
写到这里,不知道你理解元类了没?希望理解了,如果没理解,就多看几遍吧~
306+
307+
# 小结
308+
309+
- 在 Python 中,类也是一个对象。
310+
- 类创建实例,元类创建类。
311+
- 元类主要做了三件事:
312+
- 拦截类的创建
313+
- 修改类的定义
314+
- 返回修改后的类
315+
- 当你创建类时,解释器会调用元类来生成它,定义一个继承自 object 的普通类意味着调用 type 来创建它。
316+
317+
# 参考资料
318+
319+
- [oop - What is a metaclass in Python? - Stack Overflow](http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python)
320+
- [深刻理解Python中的元类(metaclass) - 伯乐在线](http://blog.jobbole.com/21351/)
321+
- [使用元类 - 廖雪峰的官方网站](http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014319106919344c4ef8b1e04c48778bb45796e0335839000)
322+
- [Python基础:元类](http://www.cnblogs.com/russellluo/p/3409602.html)
323+
- [在Python中使用class decorator和metaclass](http://blog.zhangyu.so/python/2016/02/19/class-decorator-and-metaclass-in-python/)
324+
2325

0 commit comments

Comments
 (0)