Python元类

Python元类

从某种意义上讲,元类只是扩展了装饰器的代码插入模式。
元类主要是针对那些构建API和工具供他人使用的程序员。

Python构建工具:

  1. 内省属性。如__class__、__dict__
  2. 运算符重载方法。如__str__、__add__
  3. 属性拦截方法。如__getattr__、__setattr__、__getattribute__。
  4. 类特性。内置函数property。拦截特定的属性。
  5. 类属性描述符。拦截特定的属性。特定只是定义根据访问自动运行函数的属性描述符的一种简洁方式。
  6. 函数和类装饰器。
  7. 元类。

元类允许我们在在一条class语句的末尾,插入当创建一个类对象的时候自动运行的逻辑。
这个逻辑不会把类名重新绑定到一个装饰器可调用对象,而是把类自身的创建指向特定的逻辑。

和类装饰器不同,它通常是添加实例创建时运行的逻辑,元类在类创建时运行。
同样的,它们都是通常用来管理或扩展类的钩子,而不是管理其实例。

通过声明一个元类,我们告诉Python把类对象的创建路由到我们所提供的另一个类:

由于创建类的时候,Python在class语句的末尾自动调用元类,因此它可以根据需要扩展、注册或管理类。

类也是某物的实例:

  • 在Python3.0中,用户定义的类对象是名为type的对象的实例,type本身是一个类。
  • 在Python2.6中,新式类继承自object,它是type的一个子类;传统类是type的一个实例,并且并不创建自一个类。
    实例创建自类,而类创建自type。
    类是类型,类型也是类
  • 类型由派生自type的类定义。
  • 用户定义的类是类型类的实例。
  • 用户定义的类是产生它们自己的实例的类型。

类根本不是一个独立的概念:它们就是用户定义的类型,并且type自身也是由一个类定义的。

由于类实际上是type类的实例,从type的定制的子类创建类允许我们实现各种定制的类。
在Python3.0中以及在Python2.6的新式类中:

  • type是产生用户定义的类的一个类。
  • 元类是type类的一个子类。
  • 类对象是type类的一个实例,或一个子类。
  • 实例对象产生自一个类。

换句话说,为了控制创建类以及扩展其行为的方式,我们所需要做的只是指定个用户定义的类创建自一个用户定义的元类,而不是常规的type类。

从技术上讲,Python遵从一个标准的协议来使这发生:在一条class语句的末尾,并且在运行了一个命名控件词典中的所有嵌套代码之后,它调用type对象来创建class对象:

class = type(classname, superclasses,attrbuteddict)

type对象反过来定义了一个__call__运算符重载方法,当调用type对象的时候,该方法运行两个其他方法:

type.__new__(typeclass, classname,superclasses,attributeddict)
type.__init__(class,classname,superclasses,attributedict)

__new__方法创建并返回了新的class对象,并且随后__init__方法初始化了新创建的对象。
这是type的元类子类通常用来定制类的钩子。

尽管重新定义type超类的__new____init__方法是元类向类对象创建过程插入逻辑的最常见方法,其他方案也是可能的。
实际上任何可调用对象都可以用作一个元类,只要它接收传递的参数并且返回与目标类兼容的一个对象。

另外,我们也可以重定义元类的__call__, 以拦截创建调用。

making class
In SuperMeta.call:
…Spam
…(<class ‘__main__.Eggs’>,)
…{‘meth’: , ‘data’: 1, ‘__new__’: , ‘__module__’: ‘__main__’, ‘__qualname__’: ‘Spam’, ‘__init__’: }
In SubMeta.new:
…Spam
…(<class ‘__main__.Eggs’>,)
…{‘meth’: , ‘data’: 1, ‘__new__’: , ‘__module__’: ‘__main__’, ‘__qualname__’: ‘Spam’, ‘__init__’: }
In SubMeta init:
…Spam
…(<class ‘__main__.Eggs’>,)
…{‘meth’: , ‘data’: 1, ‘__new__’: , ‘__module__’: ‘__main__’, ‘__qualname__’: ‘Spam’, ‘__init__’: }
…init class object: [‘meth’, ‘data’, ‘__new__’, ‘__doc__’, ‘__module__’, ‘__init__’]
making instance
data: 1

调用顺序是:
1. 调用元类的__call__。只调用一次。
2. 调用元类的__new__。创建一个type实例,只调用一次。
3. 调用元类的__init__。初始化type实例,只调用一次。
4. 调用自定义类的__new__。调用多次,每创建一个自定义实例,调用一次。
5. 调用自定义类的__init__。调用多次,每创建一个自定义实例,调用一次。

实例与继承的关系

  • 元类继承自type类。
  • 元类声明由子类继承。在用户定义的类中,metaclass=M声明由该类的子类继承,因此,对于在超类链中继承了这一声明的每个类的构建,该元类都将运行。
  • 元类属性没有由类实例继承。元类声明指定了一个实例关系,它和继承不同。由于类是元类的实例,所以元类中定义的行为应用于类,而不是类随后的实例。实例从它们的类和超类获取行为,但是,不是从任何元类获取行为。从技术上讲,实例属性查找通过只是搜索实例及期所有类的__dict__字典;元类不包含在实例查找中。

元类与类装饰器在功能上有重合。

  • 在class语句末尾,类装饰器把类名重新绑定到一个函数的结果。
  • 元类通过在一条class语句的末尾把类对象创建过程路由到一个对象来工作。

学习元类可参考Enum源码

  1. _EnumDict通过继承dict,并重写__setitem__,来实现枚举的name不重复。
  2. 通过重写元类EnumMeta的__delattr____setattr__来限制删除和修改枚举成员。
Tags: