Python中的元类(译)
add by zhj: 这是大stackoverflow上一位小白提出的问题,好吧,我承认我也是小白,元类这块我也是好多次想搞明白,
但终究因为太难懂而败下阵来。看了这篇文章明白了许多,再加下啄木鸟社区的 Python 类型和对象 。卧槽,
这简直就是珠联璧合,日月神剑啊,尼玛。终于理解了元类。元类就是创建类对象的类,建议用__metaclass__用于指定元类,它的好
处也就是可以要创建类之前和之后修改类的属性,创建后修改属性也可以在__init__中做,不过完全可以用__new__代替,在元类的
__new__()方法中会直接或间接调用type(classname, parentclasses , attrs),我们可以控制这三个参数,type()就是实例化type类,
即在堆上创建一个类对象,任何类对象的创建都会调用这个接口,我们可以控制让__new__()返回什么样的类对象,比如我们可以在
__new__多次调用type(),让他生成多个类对象,然后将这些类按一定的策略组合起来返回。说白了,__metaclass__是用于控制类的
生成过程。我们大多数情况下无需指定__metaclass__,这种情况下,解释器会按一定的策略找到元类。元类的参数就是我们用class关键字
定义类时的类名,类的所有父类,类中定义的{属性名:属性对象}。
翻译时部分地方有修改。
原文:http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python?answertab=votes#tab-top
类是对象
在理解元类之前,你需要先掌握Python中的类。在Python中,对类的定义比较特殊,这点借鉴了Smalltalk语言。在大多
数语言中,类只是用于描述如何创建对象的代码片,在Python中,在一定程度上也是这样:
>>> class ObjectCreator(object): ... pass ... >>> my_object = ObjectCreator() >>> print(my_object) <__main__.ObjectCreator object at 0x8974f2c>
不过,Python中的类不止如此。类也是对象,是的,我没说错,在《Python源码剖析》中提到除了Python的内置类型外
其它对象都是在堆上创建的,而内置类型应该是在内存的全局数据区。在Python中,一切都是对象,对象对用户来说只提供
了变量,变量就是那些标识符,变量以引用的方式操作对象,变量相当于对象暴露给用户的接口。Python中的引用跟C++
的引用差不多相同,不过也有差异。
当你使用关键字class时,Python解释器执行时,会创建一个对象。如下,Python会在堆中创建一个对象,并在符号表
中增加一个标识符ObjectCreator,指向那个对象的地址,这就是Python中所说的引用,我们一般称ObjectCreator为变量。
>>> class ObjectCreator(object): ... pass ...
我们称这个对象为类对象,因为该对象可以实例化,创建实例对象,因此我们称它为类。
因为它是一个对象,所以:
- 可以将它赋给一个变量
- 可以copy它
- 可以给它增加属性
- 可以作为函数参数
比如
>>> print(ObjectCreator) # 可以print it <class '__main__.ObjectCreator'> >>> def echo(o): ... print(o) ... >>> echo(ObjectCreator) # 类对象作为函数参数 <class '__main__.ObjectCreator'> >>> print(hasattr(ObjectCreator, 'new_attribute')) False >>> ObjectCreator.new_attribute = 'foo' # 增加类的属性 >>> print(hasattr(ObjectCreator, 'new_attribute')) True >>> print(ObjectCreator.new_attribute) foo >>> ObjectCreatorMirror = ObjectCreator # 赋值给另一个变量 >>> print(ObjectCreatorMirror.new_attribute) foo >>> print(ObjectCreatorMirror()) <__main__.ObjectCreator object at 0x8997b4c>
通过type类创建类
原文中说通过type类创建对象是动态,其实我们平时使用class关键字定义类也是动态的,当Python解释器遇到class关键字的,
它就会执行,并创建类对象。其实,类对象也是实例化的结果,即类对象是由另一个类实例化得到的,我们称创建类的类为元类,
反之也成立,如果类X实例化后得到是类对象,那X就是元类。在Python中,只有type类及其子类才可以当元类。多说
一句,这里会有鸡生蛋、蛋生鸡的问题。在Python中,一切都是对象,一切对象都是类实例化的结果。对象由类实例化得到,而类也是
对象,它也是由类(元类)实例化得到,继续向上,元类也是对象,也要由另一个元类实例化得到,这样下去就没有尽头了。在Python中,
这个追溯终止在type类。元类是type类或其子类,而type类的元类就是它自己,哈哈,type类自己创建自己,牛逼吧,当然type类的
这个特性是Python设计者设计并实现的。至于Python解释器在定义类时是怎么找到该类的元类的,后面我们会讲。
还记得type()方法吗?我们常用它看一个对象X所属的类,即创建该对象X的类,当对象X是类对象时,看到的就是元类。如下,
>>> print(type(1)) <type 'int'> >>> print(type("1")) <type 'str'> >>> print(type(ObjectCreator)) #查看创建ObjectCreator类的类 <type 'type'> >>> print(type(ObjectCreator())) <class '__main__.ObjectCreator'>
type类还有另外一个功能,它可以创建类,它以类的一些信息做为type()的参数,并返回一个类。我知道,type根据输入参数的不同而有不同的功能,
这种做法是愚蠢的。当type()只有一个参数时,它的功能就是返回创建该参数的类;当多于一个参数时,type()才是type类的实例化,实例化得到
一个类并返回该类。type创建类时,参数格式如下,classname是类名,字符串类型,parentclasses是类所有父类,元组类型,attrs是类的所有{属性:值},
字典类型。如果用户是用class关键字定义的类,那解释器会自动转为下面的格式执行。
type(classname, parentclasses , attrs)
比如,
>>> class MyShinyClass(object): ... pass
当解释器执行时,会转为下面的语句,当然,你也可以直接这么写。
MyShinyClass = type('MyShinyClass', (object,), {})
下面我们来定义一个类,并在类中定义属性,如
>>> class Foo(object): ... bar = True
它会被翻译成下面的形式,
>>> Foo = type('Foo', (), {'bar':True})
用用看吧
>>> print(Foo) <class '__main__.Foo'> >>> print(Foo.bar) True >>> f = Foo() >>> print(f) <__main__.Foo object at 0x8a9b84c> >>> print(f.bar) True
我们可以用另一个类继承它,so:
>>> class FooChild(Foo): ... pass
would be:
>>> FooChild = type('FooChild', (Foo,), {}) >>> print(FooChild) <class '__main__.FooChild'> >>> print(FooChild.bar) # bar is inherited from Foo True
OK,你会想给你的类增加方法。定义一个函数,并将它分配给类的属性
>>> def echo_bar(self): ... print(self.bar) ... >>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar}) >>> hasattr(Foo, 'echo_bar') False >>> hasattr(FooChild, 'echo_bar') True >>> my_foo = FooChild() >>> my_foo.echo_bar() True
说到这里,你应该已经明白了类的创建过程。那Python创建类都是这么简单吗?都是直接用type元类实例化得到?
不是的,你可以指定元类,我们会在“__metaclass__属性”一节会讲。
什么是元类
元类就是创建类的类,当通过type.__new__(cls, classname, bases, attrs)创建类时,cls就是该类的元类,
它是type类或其子类。在上面,我们提到可以使用type()查看类的元类,你也可以使用__class__,他们是完全等价的。
一切类的创建最终都会调用type.__new__(cls, classname, bases, attrs),它会在堆中创建一个类对象,并返回
>>> 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__是什么呢?__class__返回的是一个类对象,再一次__class__返回创建类对象的类,
即类的元类,如下,这个例子中类的元类都是type类,我们平时用的绝大部分类的元类都是type,不过也有例外,
有些类的元类是type的子类。当然,如果你对某个类一直调用__class__,那在有限次之后,它返回的就是type类了。
>>> age.__class__.__class__ <type 'type'> >>> name.__class__.__class__ <type 'type'> >>> foo.__class__.__class__ <type 'type'> >>> b.__class__.__class__ <type 'type'>
__metaclass__属性
你可以在类中添加__metaclass__属性,用它指定创建该类的元类,前面我们说过,元类必须是type类或type子类。
注:其实__metaclass__只要是一个可调用对象就行,该可调用对象的参数格式为callable(classname, parentclasses , attrs),
可以看到,这跟上一节用type类创建类对象时,参数格式完全相同。Python要求该对象执行时必须要调用执行type()或type子类(),
当Python解释器执行时,会调用这个可调用对象,参数也是Python解释器添加上去的。一般的,__metaclass__用元类,在本
文中,作者分别用了函数和元类。这里还有一点,子类如果指定了__metaclass__,该__metaclass__必须与父类的元类相同或
是父类元类的子类,不然,呵呵,Python解释器会选其中一个做元类,但选哪个,貌似没有什么规律。
如果我们创建类时,要指定__metaclass__属性,那最好创建的这个类的父类的元类是type类(有点绕),这样就不容易出错,
不然的话,请慎重使用元类。
class Foo(father_class): __metaclass__ = something... [...]
我们假定在继承关系上,子类的元类是父类的元类或父类的元类的子类。在这个正确的前提下,我们说一下Python解释器是怎么确定
一个类的元类的(只讨论新式类的创建,旧式类不讨论,对于新式类,Python解释器的第三步是直接使用type类,作者讨论了定义类时,
没有父类的情况,这种类我们不会用到,不讨论)。add by zhj:但后面我发现,下面这个规律并不成立,在"到底为什么要使用元类"
一节我猜想了更可能的规律,并初步验证过了。
1、在类中查找__metaclass__属性,如果找到就用,如果没有,进入2
2、在继承树中查找__metaclass__属性,如果还是没有,进入3
3、使用type类
举个例子吧,比如你想将一个类的属性全部转为大写(以__开头的属性除外),其实一种办法就是使用__metaclass__属性。
前面我们提到过,__metaclass__可以是任意可调用对象,只要调用super()或super子类()就可以。
OK,我们先使用一个函数做__metaclass__,在实际上我们一般不会用函数,而是用类。
# the metaclass will automatically get passed the same argument # that you usually pass to `type` def upper_attr(future_class_name, future_class_parents, future_class_attr): """ Return a class object, with the list of its attribute turned into uppercase. """ print "call upper_attr"
# 除以__开头的属性外,其它属性转为大写 uppercase_attr = {} for name, val in future_class_attr.items(): if not name.startswith('__'): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val # let `type` do the class creation return type(future_class_name, future_class_parents, uppercase_attr) class Foo(object): __metaclass__ = upper_attr bar = 'bip' print(hasattr(Foo, 'bar')) # Out: False print(hasattr(Foo, 'BAR')) # Out: True f = Foo() print(f.BAR) # Out: 'bip'
执行输出如下:
>>>
call upper_attr
False
True
bip
>>>
然后我们将函数换成类,__new__方法是静态方法,当创建类时,Python解释器会实例化元类
UpperAttrMetaclass(classname, parentclasses , attrs),进一步它会执行
UpperAttrMetaclass.__new__(UpperAttrMetaclass, classname, parentclasses , attrs)方法。我们在下面看到,__new__
会调用type类,其实指定__metaclass__的好处也就是可以要创建类之前修改类的属性,即在调用type(classname, parentclasses , attrs)
之前修改这三个参数,前两个参数貌似没啥好修改的,主要是修改第三个参数,第三个参数是一个字典,我们可以修改键,
即修改属性名称,也可以修改键值,即属性对象,属性对象有value/property/method。
Django的ORM就是这样干的,我们在Model中定义的字段类型是各种field类实例,但当创建该model类时,会将这些field类实例转为Python
内置的类型,如CharField()实例转为unicode或str类型,IntegerField()实例转为int型。
# remember that `type` is actually a class like `str` and `int` # so you can inherit from it class UpperAttrMetaclass(type): # __new__ is the method called before __init__ # it's the method that creates the object and returns it # while __init__ just initializes the object passed as parameter # you rarely use __new__, except when you want to control how the object # is created. # here the created object is the class, and we want to customize it # so we override __new__ # you can do some stuff in __init__ too if you wish # some advanced use involves overriding __call__ as well, but we won't # see this def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr): uppercase_attr = {} for name, val in future_class_attr.items(): if not name.startswith('__'): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val return type(future_class_name, future_class_parents, uppercase_attr)
这还不是OOP,我们直接调用了type(),我们没有使用super().__new__,下面我们修改一下
class UpperAttrMetaclass(type): def __new__(cls, clsname, bases, attrs): uppercase_attrs = {} for name, val in attrs.items(): if not name.startswith('__'): uppercase_attrs[name.upper()] = val else: uppercase_attrs[name] = val return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attrs)
元类可以做到:
- 拦截类的生成
- 修改类
- 返回修改后的类
__metaclass__为什么要用类而不是函数
__metaclass__可以接受任意可调用对象,为什么要使用类而不是函数呢?有下面几个原因
- 意图清晰。当你读到
UpperAttrMetaclass(type)
, 你知道接下来要做什么 - 你可以使用OOP
- 你可以重定义
__new__
,__init__
和__call__,不过其实你完成都在
__new__方法中完成。有些人更喜欢用
__init__
到底为什么要使用元类
那么问题来了,用元类谁最强?哈哈,开个玩笑。为什么你要使用容易出错的元类呢?
well,通常情况下你不需要使用
元类是一个高级特性,99%的用户不需要关心。如果你还在想是否需要它,那你其实不需要(真正需要使用元类的人是非常确定自己的确需要的).
Python大师Tim Peters说过:元类主要的使用场景是创建API,一个典型的例子是Django ORM。
在Django ORM中,可以如下定义model
class Person(models.Model): name = models.CharField(max_length=30) age = models.IntegerField()
但是当你下面这样做时
guy = Person(name='bob', age='35') print(guy.age)
它并不会返回一个IntegerField
实例,而是返回一个int实例。之所以会这样,是因为models.Model
定义了元类
(不过它并没有直接使用__metaclass__属性,而是使用的另一种方法定义的元类),并且它使用一些魔法将你
用几条简单语句定义的Person类转为数据库的字段。Django通过暴露给用户简单的API,并通过使用元类,使复杂
的事情对用户来说变得简单。我看了一下源码,如下。
class Model(six.with_metaclass(ModelBase)): _deferred = False def __init__(self, *args, **kwargs): signals.pre_init.send(sender=self.__class__, args=args, kwargs=kwargs) # Set up the storage for instance state self._state = ModelState() …… ……
def with_metaclass(meta, *bases): """Create a base class with a metaclass.""" return meta("NewBase", bases, {})
class ModelBase(type):
"""
Metaclass for all models.
"""
def __new__(cls, name, bases, attrs):
super_new = super(ModelBase, cls).__new__
……
……
six.with_metaclass(ModelBase)等价于ModelBase("NewBase", (), {}),第二个参数为空时,Python解释器会认为该类继承于object类,
所以这句话等价于ModelBase("NewBase", (object,), {}),我在测试中发现创建的NewBase类的元类是ModelBase类,但Python
并没有给NewBase创建__metaclass__字段,这让人疑惑,看来上面说的一个类创建时查找元类的规律并不成立。那Python解释器到底是怎
么确定NewBase的元类就是ModelBase呢?经我初步验证,应该是靠调用type.__new__(cls, classname, bases, attrs)方法时,第一个参数,
因为一切类对象最终都是通过这个方法创建的,在创建时,它的第一个参数就是被创建的类的元类,第一个参数一般是type类或其子类,类创建时会记
录下它的元类。你可能会问,如果一个类是由多个类对象组合而成的呢?没问题,如果最终的类的id与其中一个类相同,那说明并没有创建新的类,
类的id不变,类的元类就不变,如果它的id与每个类都不同,那就是创建了一个新的类,创建时还是会调用type.__new__方法,第一个参数就是
它的元类。
我非常不喜欢ModelBase("NewBase", (object,), {})这种方式来定义类,因为这样定义你很难知道NewBase的元类是谁,如果ModelBase
定义了__new__方法,且在调用type.__new__()时第一个参数是ModelBase,那NewBase的元类就是ModelBase,如果ModelBase没有重定
义__new__,那对象创建时调用的type.__new__方法,第一个参数是type,即NewBase的元类就是type类。我操,好麻烦啊,我还是推荐传统
的定义类的方式,如下,这样才能一目了然的看到NewBase的元类是ModelBase,不过有一个要求,该NewBase的__metaclass__必须是NewBase
父类的元类的子类,或者相同,这样才能确保NewBase的元类是__metaclass__指定的类。
class NewBase(object):
__metaclass__ = ModelBase
最后
看了这么多,是不是晕了,反正我有点晕,再次提醒大家,最好不要指定元类,也不要用调用type()或其子类的方式来定义类,
因为这块很容易出错。推荐大家还是有class关键字去定义类。
首先,你要知道类是能产生实例的对象。
实际上,类是元类的实例。
>>> class Foo(object): pass >>> id(Foo) 142630324
Python中的一切皆对象,这些对象分为两种:类对象和实例对象。type类的元类就是它自己。
其次,元类是复杂的,对于类的简单的改动是不需要使用元类的。你可以通过下面两种技术来改变类:
- monkey patching
- 装饰器
这里再说两句吧,在gevent库中就使用了monkey patching,它可以将Python标准库中的IO接口
替换为自己的接口,感觉好牛逼的样子,有时间看看它是怎么实现的。