python 元类

转载:https://pyzh.readthedocs.org/en/latest/python-questions-on-stackoverflow.html#id1

           http://blog.csdn.net/gzlaiyonghao/article/details/3048947

           http://hi.baidu.com/thinkinginlamp/item/3438351f6e9705426926bbde



8.6.1. 什么叫做元类

Metaclass是创建class的东西。

一个class是用来创建对象的是不是?

但是我们知道,Python中的类也是对象。

Metaclass就是用来创建类这些对象的,它们是类的类,你可以形象化地理解为:

MyClass = MetaClass()
MyObject = MyClass()

你知道, type 函数可以这样使用:

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

这是因为 type 实际上是个 metaclass , Python使用 type 这个元类来创建所有的类。

现在你是不是有疑问了,为什么 type 是小写开头的,而不是 Type 呢?既然它是个元类!

我猜,大概是因为和 str , int 来保持一致吧, str 也是一个类,用来创建字符串。

你可以检查下对象的 __class__ 属性来看看它们的类是谁. Python中万物都是对象:

>>> 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__ 属性又是谁?

>>> a = 1
>>> a.__class__.__class__
<type 'type'>
>>> name = 'bob'
>>> name.__class__.__class__
<type 'type'>

所以,元类是用来创建类的。

你可以叫元类为 类工厂

type 是Python使用的内建元类,当然,Python允许大家建立自己的元类.

8.6.2. __metaclass__ 属性

你可以在写一个类的时候加上这个属性 __metaclass__

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

这样的话,Python就会用这个元类(上例中为 something ) 来创建类 Foo

我们首先写的是 class Foo(object) ,但是Python跑到这里看到这一行时,并没有在内存中建立类 Foo

因为Python这么做的:查找它有没有 ``__metaclass__`` 属性,有的话,用指定的类来创建 ``Foo`` ,否则(也就是一般情形下),使用 ``type`` 来创建

最好还是记住上面那句话 :)

当你这么写的时候:

class Foo(Bar):
    pass

Python会这么做:

  1. 有 __metaclass__ 定义吗? 如果有,在内存中建立一个类的对象。用__metaclass__ 指定的类来创建。
  2. 如果没有找到这个属性,它会继续在父类 Bar 中找
  3. 这样一直向父类找,父类的父类。。。直到 module 级别的才停止。
  4. 如果在任何的父类中都找不到,那就用 type 创建 Foo

现在一个问题,我们可以给 __metaclass__ 赋值什么呢?

答案当然是,一个可以创建类的东西。

那么,什么才能创建一个类呢?

8.6.3. 普通的元类

设计元类的主要目的就是允许我们在类创建的时候动态的修改它,这经常用在API的设计上。

让我们举一个很纯的例子,比如你想要让一个模块中的所有类都共享一些属性,有很多办法可以做到,其中一个就是 在模块中定义一个 __metaclass__ 属性。

这样,模块中所有的类都会被 __metaclass__ 创建。

幸运的是 , __metaclass__ 可以是任何可以被调用的对象。不非要是个class,还可以是个函数。

所以,我们这么做,用一个函数来作为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.
  """

  # pick up any attribute that doesn't start with '__'
  attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
  # turn them into uppercase
  uppercase_attr = dict((name.upper(), value) for name, value in attrs)

  # let `type` do the class creation
  return type(future_class_name, future_class_parents, uppercase_attr)

__metaclass__ = upper_attr # this will affect all classes in the module

class Foo(): # global __metaclass__ won't work with "object" though
  # but we can define __metaclass__ here instead to affect only this class
  # and this will work with "object" children
  bar = 'bip'

print hasattr(Foo, 'bar')
# Out: False
print hasattr(Foo, 'BAR')
# Out: True

f = Foo()
print f.BAR
# Out: 'bip'

现在我们用一个类来作为一个metaclass:

# 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):

        attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)

        return type(future_class_name, future_class_parents, uppercase_attr)

但这样并不是很 OOP , 我们可以直接调用 type 函数,并且不覆盖父亲的 __new__方法:

class UpperAttrMetaclass(type):

    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):

        attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)

        # reuse the type.__new__ method
        # this is basic OOP, nothing magic in there
        return type.__new__(upperattr_metaclass, future_class_name,
                            future_class_parents, uppercase_attr)

你可能注意到了参数 upperattr_metaclass ,没什么特殊的,一个方法总是拿那个实例来作为第一个参数。就像寻常的 self 参数。

当然,可以这么写,我上面的例子命名不那么好:)

class UpperAttrMetaclass(type):

    def __new__(cls, name, bases, dct):

        attrs = ((name, value) for name, value in dct.items() if not name.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)

        return type.__new__(cls, name, bases, uppercase_attr)

我们可以使用 super 函数来让这个例子变得更简洁:

class UpperAttrMetaclass(type):

    def __new__(cls, name, bases, dct):

        attrs = ((name, value) for name, value in dct.items() if not name.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)

        return super(UpperAttrMetaclass, cls).__new__(cls, name, bases, uppercase_attr)

元类是个简单的魔术,只要:

  • 注入类的创建
  • 修改类
  • 返回修改后的类

8.6.4. 那么你为什么用类来作为metaclass而不是函数

既然 __metaclass__ 可以是任何可以被调用的对象,那么你为什么用类作为metaclass而不是函数呢?

几个原因:

  • 更能清楚的表达意图
  • 可以使用OOP, metaclass可以继承,重写父类,甚至使用metaclass,可以使用面向对象的特性。
  • 更好的组织代码.


元类被称为 Python 中的“深奥的巫术”。尽管你需要用到它的地方极少(除非你基于 zope 编程),可事实上它的基础理论其实令人惊讶地易懂。

一切皆对象

  • 一切皆对象
  • 一切都有类型
  •  “class”和“type”之间本质上并无不同
  • 类也是对象
  • 它们的类型是 type

以前,术语 type 用于内置类型,而术语 class 用于用户定义的类,但自 Pythoon 2.2 以来“class”和“type”本质上并无不同。

对于旧风格(old-style)类的类型是 types.ClassType

真的,这是真的

Python 2.5.1 (r251:54869, Apr 18 2007, 22:08:04)
>>> class Something(object):
...     pass
...
>>> Something
<class '__main__.Something'>
>>> type(Something)
<type 'type'>

从这里可以看出在交互式解释器中创建的类是一个 first class 的对象。

类的类是……

它的元类……

就像对象是类的实例一样,类是它的元类的实例。

调用元类可以创建类。

确切来说,Python 中的其它对象也是如此。

因此当你创建一个类时……

解释器会调用元类来生成它……

定义一个继承自 object 的普通类意味着调用 type 来创建它:

>>> help(type)

Help on class type in module __builtin__:

 

class type(object)

 |  type(object) -> the object's type

 |  type(name, bases, dict) -> a new type

type 的第二种用法尤为重要。当 Python 解释器在执行一条类定义语句时(如例子中最初的两行代码之后),它会用下面的参数调用 type

  • 字符串形式的类名
  • 元组形式的基类序列——在我们的例子中是只有一个元素的元组(’one-pl’)[1],如(object,)。
  • 包括由名字影射的类成员(类属性、方法等)的字典

简单模拟

>>> def __init__(self):
...     self.message = 'Hello World'
...
>>> def say_hello(self):
...     print self.message
...
>>> attrs = {'__init__': __init__, 'say_hello': say_hello}
>>> bases = (object,)
>>> Hello = type('Hello', bases, attrs)
>>> Hello
<class '__main__.Hello'>
>>> h = Hello()
>>> h.say_hello()
Hello World

以上代码创建了类属性的字典,然后调用 type 来创建了名为 Hello 的类。

__metaclass__ 的魔法

只要在类定义中把 __metaclass__ 设置为任意有着与 type 相同参数的可调用对象,就能够提供自定义的元类。

通常使用从 type 继承的方法:

class PointlessMetaclass(type):
    
def __new__(meta, name, bases, attrs):
        
# do stuff...
        return type.__new__(meta, name, bases, attrs)

重要的是在 __new__ 方法中我们能够读取或改变传入的用以创建新类的参数。从而能够内省属性字典和改动、增加或者删除成员。

尽管当实例化一个类时这两个函数都会被调用,但覆盖 __new__  __init__ 更为重要。__init__ 初始化一个实例,而 __new__ 的职责是创建它。因此如果元类用以自定义类的创建,就需要覆盖 type  __new__

使用新类而非仅仅提供工厂函数的原因在于如果使用工厂函数(那样只是调用 type)的话元类不会被继承。

In Action...

>>> class WhizzBang(object):
...     __metaclass__ = PointlessMetaclass
...
>>> WhizzBang
<class '__main__.WhizzBang'>
>>> type(WhizzBang)
<class '__main__.PointlessMetaClass'>

WhizzBang 是一个类,但它现在已经不是 type 的实例,而是我们自定义的元类的实例了……

这有什么用?

很好的问题,元类将用在创建使用了它的新类时调用,这里是一些关于这样做的好处的观点:

  • 装饰(Decorate)类的所有方法,用以日志记录或者性能剖分。
  • 自动 Mix-in 新方法
  • 在创建时注册类。(例如自动注册插件或从类成员创建数据库模式。)
  • 提供接口注册,功能自动发现和接口适配。
  • 类校验:防止子类化,校验所有的方法是否都有 docstrings。

最重要之处在于元类中是在最后对 type 的调用时才真正创建类,所以可以自由地随你喜欢地改变属性字典(以及名称和元组形式的基类序列)。

一些流行的 Python ORMObject Relational Mappers(对象关系影射),用以和数据库协同工作)也如此使用元类。

哦,还有因为元类是继承的,所以你能够提供一个使用了你的元类的基类,而继承自它的子类就无需显式声明它了。

但是……

我曾未需要使用它来编写代码……(我们用它来剖分,也在 Ironclad 项目广泛应用它,但我不编写这些)。

还有,这一切只适用于 Python 2.x,其中的机制在 Python 3 中已经改变了。

type(type) is type

 Python 2.6 中现在也可用使用  class decorators 来实现许多以前可能需要用元类来实现的东西。



Python号称“万物皆对象”,所以说“类”也是对象!类的实例叫对象,元类的实例叫类。也就是说,元类是类的类。这对Ruby程序员来说很好理解,因为Ruby里虚类的概念基本等同于元类,不过对于PHP程序员来说就不好理解了,下面看看语法:

先看看在Python2.6里的用法:

>>> class Foo(type):
        def __str__(self):
            return "foo"

>>> class Bar(object):
        __metaclass__ = Foo

        def __str__(self):
            return "bar"

>>> type(Bar)
<class '__main__.Foo'>
>>> type(Bar())
<class '__main__.Bar'>

>>> print(Bar)
foo
>>> print(Bar())
bar

再看看在Python3.0里的语法:

>>> class Foo(type):
        def __str__(self):
            return "foo"

>>> class Bar(object, metaclass = Foo):
        def __str__(self):
            return "bar"

>>> type(Bar)
<class '__main__.Foo'>
>>> type(Bar())
<class '__main__.Bar'>

>>> print(Bar)
foo
>>> print(Bar())
bar

元类必须从type继承,类声明的时候Python2.6和Python3.0不同:在2.6里是通过类变量__metaclass__来设置的,在3.0里是通过关键字参数metaclass来设置的。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值