|
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 | + |
2 | 325 |
|
0 commit comments