Python: 陌生的 metaclass
VicWickens
8年前
<p>Python 中的 <strong>元类(metaclass)</strong> 是一个深度魔法,平时我们可能比较少接触到元类,本文将通过一些简单的例子来理解这个魔法。</p> <h2><strong>类也是对象</strong></h2> <p>在 Python 中,一切皆对象。字符串,列表,字典,函数是对象, <strong>类也是一个对象</strong> ,因此你可以:</p> <ul> <li>把类赋值给一个变量</li> <li>把类作为函数参数进行传递</li> <li>把类作为函数的返回值</li> <li>在运行时动态地创建类</li> </ul> <p>看一个简单的例子:</p> <pre> <code class="language-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 </code></pre> <h2><strong>熟悉又陌生的 type</strong></h2> <p>在日常使用中,我们经常使用 object 来派生一个类,事实上,在这种情况下,Python 解释器会调用 type 来创建类。</p> <p>这里,出现了 type ,没错,是你知道的 type ,我们经常使用它来判断一个对象的类型,比如:</p> <pre> <code class="language-python">class Foo(object): Foo = True >>> type(10) <type 'int'> >>> type('hello') <type 'str'> >>> type(Foo()) <class '__main__.Foo'> >>> type(Foo) <type 'type'> </code></pre> <p>事实上, type 除了可以返回对象的类型,它还可以被用来动态地创建类(对象) 。下面,我们看几个例子,来消化一下这句话。</p> <p>使用 type 来创建类(对象)的方式如下:</p> <p>type(类名, 父类的元组(针对继承的情况,可以为空),包含属性和方法的字典(名称和值))</p> <h3><strong>最简单的情况</strong></h3> <p>假设有下面的类:</p> <pre> <code class="language-python">class Foo(object): pass </code></pre> <p>现在,我们不使用 class 关键字来定义,而使用 type ,如下:</p> <pre> <code class="language-python">Foo = type('Foo', (object, ), {}) # 使用 type 创建了一个类对象 </code></pre> <p>上面两种方式是等价的。我们看到, type 接收三个参数:</p> <ul> <li>第 1 个参数是字符串 ‘Foo’,表示类名</li> <li>第 2 个参数是元组 (object, ),表示所有的父类</li> <li>第 3 个参数是字典,这里是一个空字典,表示没有定义属性和方法</li> </ul> <p>在上面,我们使用 type() 创建了一个名为 Foo 的类,然后把它赋给了变量 Foo,我们当然可以把它赋给其他变量,但是,此刻没必要给自己找麻烦。</p> <p>接着,我们看看使用:</p> <pre> <code class="language-python">>>> print Foo <class '__main__.Foo'> >>> print Foo() <__main__.Fooobject at 0x10c34f250> </code></pre> <h3><strong>有属性和方法的情况</strong></h3> <p>假设有下面的类:</p> <pre> <code class="language-python">class Foo(object): foo = True defgreet(self): print 'hello world' printself.foo </code></pre> <p>用 type 来创建这个类,如下:</p> <pre> <code class="language-python">defgreet(self): print 'hello world' printself.foo Foo = type('Foo', (object, ), {'foo': True, 'greet': greet}) </code></pre> <p>上面两种方式的效果是一样的,看下使用:</p> <pre> <code class="language-python">>>> f = Foo() >>> f.foo True >>> f.greet <boundmethodFoo.greetof <__main__.Fooobject at 0x10c34f890>> >>> f.greet() helloworld True </code></pre> <h3><strong>继承的情况</strong></h3> <p>再来看看继承的情况,假设有如下的父类:</p> <pre> <code class="language-python">class Base(object): pass </code></pre> <p>我们用 Base 派生一个 Foo 类,如下:</p> <pre> <code class="language-python">class Foo(Base): foo = True </code></pre> <p>改用 type 来创建,如下:</p> <pre> <code class="language-python">Foo = type('Foo', (Base, ), {'foo': True}) </code></pre> <h2><strong>什么是元类(metaclass)</strong></h2> <p>元类(metaclass)是用来创建类(对象)的可调用对象。这里的可调用对象可以是函数或者类等。但一般情况下,我们使用类作为元类。对于实例对象、类和元类,我们可以用下面的图来描述:</p> <pre> <code class="language-python">类是实例对象的模板,元类是类的模板 +----------+ +----------+ +----------+ | | | | | | | | instanceof | | instanceof | | | instance +------------>+ class +------------>+ metaclass| | | | | | | | | | | | | +----------+ +----------+ +----------+ </code></pre> <p>我们在前面使用了 type 来创建类(对象),事实上, <strong> type 就是一个元类 </strong> 。</p> <p>那么,元类到底有什么用呢?要你何用…</p> <p>元类的主要目的是为了控制类的创建行为。我们还是先来看看一些例子,以消化这句话。</p> <h2><strong>元类的使用</strong></h2> <p>先从一个简单的例子开始,假设有下面的类:</p> <pre> <code class="language-python">class Foo(object): name = 'foo' defbar(self): print 'bar' </code></pre> <p>现在我们想给这个类的方法和属性名称前面加上 my_ 前缀,即 name 变成 my_name,bar 变成 my_bar,另外,我们还想加一个 echo 方法。当然,有很多种做法,这里展示用元类的做法。</p> <p>1.首先,定义一个元类,按照默认习惯,类名以 Metaclass 结尾,代码如下:</p> <pre> <code class="language-python">class PrefixMetaclass(type): def__new__(cls, name, bases, attrs): # 给所有属性和方法前面加上前缀 my_ _attrs = (('my_' + name, value) for name, valuein attrs.items()) _attrs = dict((name, value) for name, valuein _attrs) # 转化为字典 _attrs['echo'] = lambdaself, phrase: phrase # 增加了一个 echo 方法 return type.__new__(cls, name, bases, _attrs) # 返回创建后的类 </code></pre> <p>上面的代码有几个需要注意的点:</p> <ul> <li>PrefixMetaClass 从 type 继承,这是因为 PrefixMetaclass 是用来创建类的</li> <li>__new__ 是在 __init__ 之前被调用的特殊方法,它用来创建对象并返回创建后的对象,对它的参数解释如下: <ul> <li>cls:当前准备创建的类</li> <li>name:类的名字</li> <li>bases:类的父类集合</li> <li>attrs:类的属性和方法,是一个字典</li> </ul> </li> </ul> <p>2.接着,我们需要指示 Foo 使用 PrefixMetaclass 来定制类。</p> <p>在 Python2 中,我们只需在 Foo 中加一个 __metaclass__ 的属性,如下:</p> <pre> <code class="language-python">class Foo(object): __metaclass__ = PrefixMetaclass name = 'foo' defbar(self): print 'bar' </code></pre> <p>在 Python3 中,这样做:</p> <pre> <code class="language-python">class Foo(metaclass=PrefixMetaclass): name = 'foo' defbar(self): print 'bar' </code></pre> <p>现在,让我们看看使用:</p> <pre> <code class="language-python">>>> f = Foo() >>> f.name # name 属性已经被改变 --------------------------------------------------------------------------- AttributeError Traceback (mostrecentcalllast) <ipython-input-774-4511c8475833> in <module>() ----> 1 f.name AttributeError: 'Foo' object hasnoattribute 'name' >>> >>> f.my_name 'foo' >>> f.my_bar() bar >>> f.echo('hello') 'hello' </code></pre> <p>可以看到,Foo 原来的属性 name 已经变成了 my_name,而方法 bar 也变成了 my_bar,这就是元类的魔法。</p> <p>再来看一个继承的例子,下面是完整的代码:</p> <pre> <code class="language-python">class PrefixMetaclass(type): def__new__(cls, name, bases, attrs): # 给所有属性和方法前面加上前缀 my_ _attrs = (('my_' + name, value) for name, valuein attrs.items()) _attrs = dict((name, value) for name, valuein _attrs) # 转化为字典 _attrs['echo'] = lambdaself, phrase: phrase # 增加了一个 echo 方法 return type.__new__(cls, name, bases, _attrs) class Foo(object): __metaclass__ = PrefixMetaclass # 注意跟 Python3 的写法有所区别 name = 'foo' defbar(self): print 'bar' class Bar(Foo): prop = 'bar' </code></pre> <p>其中,PrefixMetaclass 和 Foo 跟前面的定义是一样的,只是新增了 Bar,它继承自 Foo。先让我们看看使用:</p> <pre> <code class="language-python">>>> b = Bar() >>> b.prop # 发现没这个属性 --------------------------------------------------------------------------- AttributeError Traceback (mostrecentcalllast) <ipython-input-778-825e0b6563ea> in <module>() ----> 1 b.prop AttributeError: 'Bar' object hasnoattribute 'prop' >>> b.my_prop 'bar' >>> b.my_name 'foo' >>> b.my_bar() bar >>> b.echo('hello') 'hello' </code></pre> <p>我们发现,Bar 没有 prop 这个属性,但是有 my_prop 这个属性,这是为什么呢?</p> <p>原来,当我们定义 class Bar(Foo) 时,Python 会首先在当前类,即 Bar 中寻找 __metaclass__ ,如果没有找到,就会在父类 Foo 中寻找 __metaclass__ ,如果找不到,就继续在 Foo 的父类寻找,如此继续下去,如果在任何父类都找不到 __metaclass__ ,就会到模块层次中寻找,如果还是找不到,就会用 type 来创建这个类。</p> <p>这里,我们在 Foo 找到了 __metaclass__ ,Python 会使用 PrefixMetaclass 来创建 Bar,也就是说,元类会隐式地继承到子类,虽然没有显示地在子类使用 __metaclass__ ,这也解释了为什么 Bar 的 prop 属性被动态修改成了 my_prop。</p> <p>写到这里,不知道你理解元类了没?希望理解了,如果没理解,就多看几遍吧~</p> <h2><strong>小结</strong></h2> <ul> <li>在 Python 中,类也是一个对象。</li> <li>类创建实例,元类创建类。</li> <li>当你创建类时,解释器会调用元类来生成它,定义一个继承自 object 的普通类意味着调用 type 来创建它。</li> </ul> <h2><strong>参考资料</strong></h2> <ul> <li><a href="/misc/goto?guid=4959549103024326777" rel="nofollow,noindex">oop – What is a metaclass in Python? – Stack Overflow</a></li> <li><a href="/misc/goto?guid=4959653571151597157" rel="nofollow,noindex">深刻理解Python中的元类(metaclass) – 伯乐在线</a></li> <li><a href="/misc/goto?guid=4959725574622362752" rel="nofollow,noindex">使用元类 – 廖雪峰的官方网站</a></li> </ul> <p> </p> <p>来自:http://python.jobbole.com/86821/</p> <p> </p>