• Python——五分钟理解元类(metaclasses)


    “元类的魔幻变化比 99% 的用户所担心的更多,当你搞不懂是否真的需要用它的时候,就是不需要。”

    —Tim Peters

    本文源于在 PyCon UK 2008 上的一个快速演讲。

    元类被称为 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 ORM(Object Relational Mappers(对象关系影射),用以和数据库协同工作)也如此使用元类。

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

    但是……

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

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

    type(type) is type

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

    最后,还有一个极尽奇技淫巧的例子(稍为深入,但仍然不难消化),可以去看看 The Selfless Metaclass。它通过字节码和方法签名重写来避免显式地声明 self 。

    [1]

     'one-pl'是指只有一个元素的元组。

  • 相关阅读:
    Vue学习笔记
    用vue-cli3搭建vue项目
    Vue 封装可向左向右查看图片列表的组件
    css修改整个项目的滚动条样式
    Vue 可输入可下拉组件的封装
    es6 实现数组的操作
    JS 实现兼容IE浏览器报警提示声音
    SPRINGBOOT9--AOP的使用(本例展示统一处理Web请求日志)
    SPRINGBOOT8--log4j日志记录
    SPRINGBOOT7--使用@Async实现异步调用
  • 原文地址:https://www.cnblogs.com/Simon-xm/p/3914288.html
Copyright © 2020-2023  润新知