python中的__metaclass__

什么是元类:

  python中类也是一种对象, 可以称为类对象.

  元类就是用来创建类对象的"东西". 你创建类就是为了创建类的实例对象, 不是吗? 但是我们已经学习了python中的类也是对象. 元类就是用来创建这些类对象的, 元类就是类的类, 你可以这样理解:

MyClass = MetaClass()
MyObject = MyClass()

你已经看到了type可以这样来动态的创建类:

MyClass = type("MyClass", (), {})

这是因为type实际上是一个元类. type就是python在背后用来创建所有类的元类. 那么现在你肯定特别想知道type既然是一个类(元类), 为什么首字母没有大写? 好吧, 我猜这是为了与int, str, function这些python内置数据类型保持一致, str是用来创建字符串对象的类, 而int是用来创建整数对象的类, type就是用来创建类对象的类. 你可以通过检查__class__属性来证明这一点. python中所有的东西都是对象(这一点和javascript相同), 这包括整数, 字符串, 函数以及类, 它们全部都是对象, 而且它们都是从一个类创建而来, 这个类就是type.

>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
... 
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
... 
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>

看一下上面这些任何一个__class__的__class__属性又是什么呢?

>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>
>>> Bar.__class__
<type 'type'>

从上面结果可以看出, python中的所有普通的类也是对象, 当然这些类对象也有类型. 可以看出, int, str, 函数, 普通自定义类, 这些类对象的类型都是type.

  元类就是创建类对象的类. 如果你喜欢, 可以把元类看做"类工厂". type就是python的内建元类, 当然也可以创建自己的元类.

__metaclass__属性:

  你可以在实现一个类的时候为其添加__metaclass__属性:

class Foo(object):
    __metaclass__ = something
    ...

如果你这么做了, python就会用元类来创建类Foo. 当在内存中创建类对象Foo时, python会在类的定义中寻找__metaclass__属性, 如果找到了, python就用它来创建Foo, 如果没有找到, 就会用内建的type元类来创建这个类.

当你实现一个有继承的类时:

class Foo(Bar):
    pass

python会做如下操作:

Foo中有__metaclass__这个属性吗? 如果是, python会在内存中通过__metaclass__创建一个名字为Foo的类对象. 如果python没有找到__metaclass__, 它会继续在Bar(父类)中寻找__metaclass__, 以此类推. 如果python在任何父类中都没有找到__metaclass__, 它就会在模块层次中寻找__metaclass__. 如果还是找不到__metaclass__, python就会用内置的type元类来创建这个类对象.

自定义元类:

  元类的主要目的是为了当创建类时能够自动的改变类, 或者说定制类. 为什么这么说呢, 我们可以这样理解: 把类的创建过成想象成一个管道, 如果遍历完继承链+模块层次都没有发现__metaclass__属性, 那么用type元类来创建该类. 这种情况下我们在类中怎样定义属性, 最终生成的类对象也会具有怎样的属性, 如下图所示:

类创建时的样子---(type)-->类最终的样子(由于是用type元类, 所以类最终样式与类创建时样式一样)

如果遍历过程中找到了__metaclass__属性, 那么就会用自定义元类来创建该类, 如下图所示:

类创建时的样子---(__metaclass__属性指向的元类)--->类最终的样子

由于在我们自定义的元类里面可以定制那些__metaclass__指过来的类, 因此元类的主要目的一般是为了当创建类时自动的改变类, 或者说定制类.

  到底什么时候才会用到元类呢? 通常你会为API做这样的事情, 你希望可以创建复合当前上下文的类. 假想一个很傻的例子, 你希望在你的模块里所有类的属性都应该是大写形式. 有好几种方法可以办到, 但其中一种就是通过在模块级别设定__metaclass__. 采用这种方法, 这个模块中的所有类都会通过这个元类来创建, 我们只需要告诉元类把所有的属性都改成大写形式就行:

# !/usr/bin/python
# -*- coding: utf-8 -*-
# type实际上是一个类, 就像str和int一样, 所以你可以从type继承
class UpperAttrMetaClass(type):
    def __new__(cls, name, bases, dct):
        attrs = ((name, value) for name, value in dct.items() if not name.startswith('__'))
        uppercase_attrs = dict((name.upper(), value) for name, value in attrs)
        return type.__new__(cls, name, bases, uppercase_attrs)

class Foo(object):
    __metaclass__ = UpperAttrMetaClass
    bar = 'bip'

print hasattr(Foo, 'bar')
# 输出false
print hasattr(Foo, 'BAR')
# 输出true

f = Foo()
print f.BAR
# 输出bip

也可以使用super方法:

class UpperAttrMetaClass(type):
    def __new__(cls, name, bases, dct):
        attrs = ((name, value) for name, value in dct.items() if not name.startswith('__'))
        uppercase_attrs = dict((name.upper(), value) for name, value in attrs)
        #return type.__new__(cls, name, bases, uppercase_attrs)
        return super(UpperAttrMetaClass, cls).__new__(cls, name, bases, uppercase_attrs)

就是这样, 除此之外关于元类真的没有别的可以说的了. 就元类本身而言, 它其实很简单:

1. 拦截类的创建

2. 修改类(或者说定制类)

3. 返回修改之后的类

究竟为什么要使用元类:

元类的主要作用是创建API. 一个典型的例子是Django ORM. 它允许你像这样定义类:

class Person(models.Model):
    name = models.CharField(max_length = 30)
    age = models.IntegerField()

 但是如果你像这样做的话:

guy = Person(name = "bob", age = "35")
print guy.age

调用guy.age并不会返回一个IntegerField对象, 而是会返回一个int, 是指可以直接从数据库中取出数据. 这是有可能的, 因为models.Model定义了__metaclass__, 并且在元类中将你刚刚定义的简单的Person类给修改了, 变的能够访问数据库了. Django框架将这些看起来很复杂的东西通过暴露出一个简单的使用元类的API(models.Model)将其化简, 通过这个API重新创建代码, 在背后完成真正的工作.

结语:

  现在我们知道了, 类本身也是实例, 只不过它们是元类的实例. python中的一切都是对象, 这些对象要么是类对象的实例, 要么是元类的实例, 除了type. 元类是比较复杂的, 大多数情况下, 我们不会对类做修改. 如果需要对类做修改,  大多数情况下我们会通过下面两种技术来动态修改类:

1. Monkey patching

2. class decorators

 

posted @ 2015-08-08 21:09  iamswf  阅读(3667)  评论(0编辑  收藏  举报