Python: 陌生的 metaclass

VicWickens 7年前
   <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>