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

1. 类对象

2. 通过type类创建类

3. 什么是元类

4. __metaclass__属性

5. __metaclass__为什么要用类而不是函数

6. 到底为什么要使用元类

7. 最后

类是对象

在理解元类之前,你需要先掌握Python中的类。在Python中,对类的定义比较特殊,这点借鉴了Smalltalk语言。在大多

数语言中,类只是用于描述如何创建对象的代码片,在Python中,在一定程度上也是这样:

  1. >>> class ObjectCreator(object):
  2. ... pass
  3. ...
  4.  
  5. >>> my_object = ObjectCreator()
  6. >>> print(my_object)
  7. <__main__.ObjectCreator object at 0x8974f2c>

不过,Python中的类不止如此。类也是对象,是的,我没说错,在《Python源码剖析》中提到除了Python的内置类型外

其它对象都是在堆上创建的,而内置类型应该是在内存的全局数据区。在Python中,一切都是对象,对象对用户来说只提供

了变量,变量就是那些标识符,变量以引用的方式操作对象,变量相当于对象暴露给用户的接口。Python中的引用跟C++

的引用差不多相同,不过也有差异。

当你使用关键字class时,Python解释器执行时,会创建一个对象。如下,Python会在堆中创建一个对象,并在符号表

中增加一个标识符ObjectCreator,指向那个对象的地址,这就是Python中所说的引用,我们一般称ObjectCreator为变量。

  1. >>> class ObjectCreator(object):
  2. ... pass
  3. ...

我们称这个对象为类对象,因为该对象可以实例化,创建实例对象,因此我们称它为类。

因为它是一个对象,所以:

  • 可以将它赋给一个变量
  • 可以copy它
  • 可以给它增加属性
  • 可以作为函数参数

比如

  1. >>> print(ObjectCreator) # 可以print it
  2. <class '__main__.ObjectCreator'>
  3. >>> def echo(o):
  4. ... print(o)
  5. ...
  6. >>> echo(ObjectCreator) # 类对象作为函数参数
  7. <class '__main__.ObjectCreator'>
  8. >>> print(hasattr(ObjectCreator, 'new_attribute'))
  9. False
  10. >>> ObjectCreator.new_attribute = 'foo' # 增加类的属性
  11. >>> print(hasattr(ObjectCreator, 'new_attribute'))
  12. True
  13. >>> print(ObjectCreator.new_attribute)
  14. foo
  15. >>> ObjectCreatorMirror = ObjectCreator # 赋值给另一个变量
  16. >>> print(ObjectCreatorMirror.new_attribute)
  17. foo
  18. >>> print(ObjectCreatorMirror())
  19. <__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是类对象时,看到的就是元类。如下,

  1. >>> print(type(1))
  2. <type 'int'>
  3. >>> print(type(""))
  4. <type 'str'>
  5. >>> print(type(ObjectCreator)) #查看创建ObjectCreator类的类
  6. <type 'type'>
  7. >>> print(type(ObjectCreator()))
  8. <class '__main__.ObjectCreator'>

type类还有另外一个功能,它可以创建类,它以类的一些信息做为type()的参数,并返回一个类。我知道,type根据输入参数的不同而有不同的功能,

这种做法是愚蠢的。当type()只有一个参数时,它的功能就是返回创建该参数的类;当多于一个参数时,type()才是type类的实例化,实例化得到

一个类并返回该类。type创建类时,参数格式如下,classname是类名,字符串类型,parentclasses是类所有父类,元组类型,attrs是类的所有{属性:值},

字典类型。如果用户是用class关键字定义的类,那解释器会自动转为下面的格式执行。

  1. type(classname, parentclasses , attrs)

比如,

  1. >>> class MyShinyClass(object):
  2. ... pass

当解释器执行时,会转为下面的语句,当然,你也可以直接这么写。

  1. MyShinyClass = type('MyShinyClass', (object,), {})

下面我们来定义一个类,并在类中定义属性,如

  1. >>> class Foo(object):
  2. ... bar = True

它会被翻译成下面的形式,

  1. >>> Foo = type('Foo', (), {'bar':True})

用用看吧

  1. >>> print(Foo)
  2. <class '__main__.Foo'>
  3. >>> print(Foo.bar)
  4. True
  5. >>> f = Foo()
  6. >>> print(f)
  7. <__main__.Foo object at 0x8a9b84c>
  8. >>> print(f.bar)
  9. True

我们可以用另一个类继承它,so:

  1. >>> class FooChild(Foo):
  2. ... pass

would be:

  1. >>> FooChild = type('FooChild', (Foo,), {})
  2. >>> print(FooChild)
  3. <class '__main__.FooChild'>
  4. >>> print(FooChild.bar) # bar is inherited from Foo
  5. True

OK,你会想给你的类增加方法。定义一个函数,并将它分配给类的属性

  1. >>> def echo_bar(self):
  2. ... print(self.bar)
  3. ...
  4. >>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
  5. >>> hasattr(Foo, 'echo_bar')
  6. False
  7. >>> hasattr(FooChild, 'echo_bar')
  8. True
  9. >>> my_foo = FooChild()
  10. >>> my_foo.echo_bar()
  11. True

说到这里,你应该已经明白了类的创建过程。那Python创建类都是这么简单吗?都是直接用type元类实例化得到?

不是的,你可以指定元类,我们会在“__metaclass__属性”一节会讲。

什么是元类

元类就是创建类的类,当通过type.__new__(cls, classname, bases, attrs)创建类时,cls就是该类的元类,

它是type类或其子类。在上面,我们提到可以使用type()查看类的元类,你也可以使用__class__,他们是完全等价的。

一切类的创建最终都会调用type.__new__(cls, classname, bases, attrs),它会在堆中创建一个类对象,并返回

  1. >>> age = 35
  2. >>> age.__class__
  3. <type 'int'>
  4. >>> name = 'bob'
  5. >>> name.__class__
  6. <type 'str'>
  7. >>> def foo(): pass
  8. >>> foo.__class__
  9. <type 'function'>
  10. >>> class Bar(object): pass
  11. >>> b = Bar()
  12. >>> b.__class__
  13. <class '__main__.Bar'>

那__class__.__class__是什么呢?__class__返回的是一个类对象,再一次__class__返回创建类对象的类,

即类的元类,如下,这个例子中类的元类都是type类,我们平时用的绝大部分类的元类都是type,不过也有例外,

有些类的元类是type的子类。当然,如果你对某个类一直调用__class__,那在有限次之后,它返回的就是type类了。

  1. >>> age.__class__.__class__
  2. <type 'type'>
  3. >>> name.__class__.__class__
  4. <type 'type'>
  5. >>> foo.__class__.__class__
  6. <type 'type'>
  7. >>> b.__class__.__class__
  8. <type 'type'>

__metaclass__属性

你可以在类中添加__metaclass__属性,用它指定创建该类的元类,前面我们说过,元类必须是type类或type子类。

注:其实__metaclass__只要是一个可调用对象就行,该可调用对象的参数格式为callable(classname, parentclasses , attrs),

可以看到,这跟上一节用type类创建类对象时,参数格式完全相同。Python要求该对象执行时必须要调用执行type()或type子类(),

当Python解释器执行时,会调用这个可调用对象,参数也是Python解释器添加上去的。一般的,__metaclass__用元类,在本

文中,作者分别用了函数和元类。这里还有一点,子类如果指定了__metaclass__,该__metaclass__必须与父类的元类相同或

是父类元类的子类,不然,呵呵,Python解释器会选其中一个做元类,但选哪个,貌似没有什么规律。

如果我们创建类时,要指定__metaclass__属性,那最好创建的这个类的父类的元类是type类(有点绕),这样就不容易出错,

不然的话,请慎重使用元类。

  1. class Foo(father_class):
  2. __metaclass__ = something...
  3. [...]

我们假定在继承关系上,子类的元类是父类的元类或父类的元类的子类。在这个正确的前提下,我们说一下Python解释器是怎么确定

一个类的元类的(只讨论新式类的创建,旧式类不讨论,对于新式类,Python解释器的第三步是直接使用type类,作者讨论了定义类时,

没有父类的情况,这种类我们不会用到,不讨论)。add by zhj:但后面我发现,下面这个规律并不成立,在"到底为什么要使用元类"

一节我猜想了更可能的规律,并初步验证过了。

1、在类中查找__metaclass__属性,如果找到就用,如果没有,进入2

2、在继承树中查找__metaclass__属性,如果还是没有,进入3

3、使用type类

举个例子吧,比如你想将一个类的属性全部转为大写(以__开头的属性除外),其实一种办法就是使用__metaclass__属性。

前面我们提到过,__metaclass__可以是任意可调用对象,只要调用super()或super子类()就可以。

OK,我们先使用一个函数做__metaclass__,在实际上我们一般不会用函数,而是用类。

  1. # the metaclass will automatically get passed the same argument
  2. # that you usually pass to `type`
  3. def upper_attr(future_class_name, future_class_parents, future_class_attr):
  4. """
  5. Return a class object, with the list of its attribute turned
  6. into uppercase.
  7. """
  8. print "call upper_attr"
  9.  
  10. # 除以__开头的属性外,其它属性转为大写
  11. uppercase_attr = {}
  12. for name, val in future_class_attr.items():
  13. if not name.startswith('__'):
  14. uppercase_attr[name.upper()] = val
  15. else:
  16. uppercase_attr[name] = val
  17.  
  18. # let `type` do the class creation
  19. return type(future_class_name, future_class_parents, uppercase_attr)
  20.  
  21. class Foo(object):
  22. __metaclass__ = upper_attr
  23. bar = 'bip'
  24.  
  25. print(hasattr(Foo, 'bar'))
  26. # Out: False
  27. print(hasattr(Foo, 'BAR'))
  28. # Out: True
  29.  
  30. f = Foo()
  31. print(f.BAR)
  32. # 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型。

  1. # remember that `type` is actually a class like `str` and `int`
  2. # so you can inherit from it
  3. class UpperAttrMetaclass(type):
  4. # __new__ is the method called before __init__
  5. # it's the method that creates the object and returns it
  6. # while __init__ just initializes the object passed as parameter
  7. # you rarely use __new__, except when you want to control how the object
  8. # is created.
  9. # here the created object is the class, and we want to customize it
  10. # so we override __new__
  11. # you can do some stuff in __init__ too if you wish
  12. # some advanced use involves overriding __call__ as well, but we won't
  13. # see this
  14. def __new__(upperattr_metaclass, future_class_name,
  15. future_class_parents, future_class_attr):
  16.  
  17. uppercase_attr = {}
  18. for name, val in future_class_attr.items():
  19. if not name.startswith('__'):
  20. uppercase_attr[name.upper()] = val
  21. else:
  22. uppercase_attr[name] = val
  23.  
  24. return type(future_class_name, future_class_parents, uppercase_attr)

这还不是OOP,我们直接调用了type(),我们没有使用super().__new__,下面我们修改一下

  1. class UpperAttrMetaclass(type):
  2.  
  3. def __new__(cls, clsname, bases, attrs):
  4.  
  5. uppercase_attrs = {}
  6. for name, val in attrs.items():
  7. if not name.startswith('__'):
  8. uppercase_attrs[name.upper()] = val
  9. else:
  10. uppercase_attrs[name] = val
  11.  
  12. 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

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

但是当你下面这样做时

  1. guy = Person(name='bob', age='')
  2. print(guy.age)

它并不会返回一个IntegerField实例,而是返回一个int实例。之所以会这样,是因为models.Model 定义了元类

(不过它并没有直接使用__metaclass__属性,而是使用的另一种方法定义的元类),并且它使用一些魔法将你

用几条简单语句定义的Person类转为数据库的字段。Django通过暴露给用户简单的API,并通过使用元类,使复杂

的事情对用户来说变得简单。我看了一下源码,如下。

  1. class Model(six.with_metaclass(ModelBase)):
  2. _deferred = False
  3.  
  4. def __init__(self, *args, **kwargs):
  5. signals.pre_init.send(sender=self.__class__, args=args, kwargs=kwargs)
  6.  
  7. # Set up the storage for instance state
  8. self._state = ModelState()
  9. ……
  10. ……
  1. def with_metaclass(meta, *bases):
  2. """Create a base class with a metaclass."""
  3. return meta("NewBase", bases, {})
  1. class ModelBase(type):
  2. """
  3. Metaclass for all models.
  4. """
  5. def __new__(cls, name, bases, attrs):
  6. super_new = super(ModelBase, cls).__new__
  7. ……
  8. ……

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关键字去定义类。

首先,你要知道类是能产生实例的对象。

实际上,类是元类的实例。

  1. >>> class Foo(object): pass
  2. >>> id(Foo)
  3. 142630324

Python中的一切皆对象,这些对象分为两种:类对象和实例对象。type类的元类就是它自己。

其次,元类是复杂的,对于类的简单的改动是不需要使用元类的。你可以通过下面两种技术来改变类:

这里再说两句吧,在gevent库中就使用了monkey patching,它可以将Python标准库中的IO接口

替换为自己的接口,感觉好牛逼的样子,有时间看看它是怎么实现的。

Python中的元类(译)的更多相关文章

  1. Python中的元类(metaclass)

    推荐+收藏:深刻理解Python中的元类(metaclass) 做一些笔记学习学习: 在大多数编程语言中,类就是用来描述如何生成一个对象的代码段,在Python中类也是一个对象,这个(类)对象自身拥有 ...

  2. [转]深刻理解Python中的元类(metaclass)以及元类实现单例模式

    使用元类 深刻理解Python中的元类(metaclass)以及元类实现单例模式 在看一些框架源代码的过程中碰到很多元类的实例,看起来很吃力很晦涩:在看python cookbook中关于元类创建单例 ...

  3. 深刻理解Python中的元类metaclass(转)

    本文由 伯乐在线 - bigship 翻译 英文出处:stackoverflow 译文:http://blog.jobbole.com/21351/ 译注:这是一篇在Stack overflow上很热 ...

  4. 深刻理解Python中的元类(metaclass)

    译注:这是一篇在Stack overflow上很热的帖子.提问者自称已经掌握了有关Python OOP编程中的各种概念,但始终觉得元类(metaclass)难以理解.他知道这肯定和自省有关,但仍然觉得 ...

  5. [转] 深刻理解Python中的元类(metaclass)

    非常详细的一篇深入讲解Python中metaclass的文章,感谢伯乐在线-bigship翻译及作者,转载收藏. 本文由 伯乐在线 - bigship 翻译.未经许可,禁止转载!英文出处:stacko ...

  6. Python中的元类

    从前面"Python对象"文章中了解到,在Python中一切都是对象,类可以创建实例对象,但是类本身也是对象. class C(object): pass c = C() prin ...

  7. 深刻理解Python中的元类(metaclass)【转】

    译注:这是一篇在Stack overflow上很热的帖子.提问者自称已经掌握了有关Python OOP编程中的各种概念,但始终觉得元类(metaclass)难以理解.他知道这肯定和自省有关,但仍然觉得 ...

  8. 深刻理解Python中的元类(metaclass)以及元类实现单例模式

    在理解元类之前,你需要先掌握Python中的类.Python中类的概念借鉴于Smalltalk,这显得有些奇特.在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段.在Python中这一点仍 ...

  9. 深入理解Python中的元类(metaclass)

    原文 译注:这是一篇在Stack overflow上很热的帖子.提问者自称已经掌握了有关Python OOP编程中的各种概念,但始终觉得元类(metaclass)难以理解.他知道这肯定和自省有关,但仍 ...

随机推荐

  1. xml布局解析报错的可能原因

    xml布局解析报如下的错11-15 16:55:21.425 17633-17633/com.hongfans.mobileconnect I/LogUtils_info: [CrashHandler ...

  2. T-SQL 类型转换

    use StudentManageDB go --定义变量并查询 declare @sumScore int select @sumScore=(CSharp+SQLServerDB) from Sc ...

  3. MySQL 迁移并搭建主从(实践)

    第一阶段 一.数据的初始化 1.老主库 关闭sql_log_binset sql_log_bin = off; 创建导出用户grant all privileges on *.* to 'dump'@ ...

  4. ZooKeeper系列(7):ZooKeeper一致性原理

    一.ZooKeeper 的实现 1.1 ZooKeeper处理单点故障 我们知道可以通过ZooKeeper对分布式系统进行Master选举,来解决分布式系统的单点故障,如图所示. 图 1.1 ZooK ...

  5. C#窗体嵌入SetParent的用法

    模块化的开发,将模块合并到一起的时候,遇到了Mdi不能添加到其它窗口下的问题. 分两种情况: 将mdi窗口A设成普通窗口B的子控件,需要将A的TopLevel设置成false,但是Mdi窗口的TopL ...

  6. Java - 27 Java 集合框架

    Java 集合框架 早在Java 2中之前,Java就提供了特设类.比如:Dictionary, Vector, Stack, 和Properties这些类用来存储和操作对象组. 虽然这些类都非常有用 ...

  7. Solr——从postgresql数据库导入数据

    1,配置准备 本文的前提是你已经配置好了solr,并新创建了一个core,我们下面都会按照前一篇文章中的core_demo为基础开始 2,修改soreconfig.xml 在soreconfig.xm ...

  8. JS控制函数执行次数(可带参数)

    //真正要执行的函数 var sayHi = function () { console.log('Hi'); }; //控制器 var timer = function (fn, num) { fo ...

  9. websocket 群聊,单聊,加密,解密

    群聊 from flask import Flask, request, render_templatefrom geventwebsocket.handler import WebSocketHan ...

  10. RPC通信原理

    什么是 RPCRPC(Remote Procedure Call Protocol)远程过程调用协议.通俗的描述是:客户端在不知道调用细节的情况下,调用存在于远程计算上的某个过程或函数,就像调用本地应 ...