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