>>> from env_helper import info; info()
页面更新时间: 2023-12-27 10:44:24
运行环境:
    Linux发行版本: Debian GNU/Linux 12 (bookworm)
    操作系统内核: Linux-6.1.0-16-amd64-x86_64-with-glibc2.36
    Python版本: 3.11.2

6.9. 掌握metaclass

什么是元类(metaclass) ?也许我们对下面这些说法都不陌生:

  • 元类是关于类的类,是类的模板。

  • 元类是用来控制如何创建类的.正如类是创建对象的模板一样。

  • 元类的实例为类,正如类的实例为对象。

这些说法都没有错,在概念之外我们来进行一些更深人的探讨:元类是如何来控制类的 创建的?用户该如何定义自己的元类?在哪些情况下需要用到元类?使用元类可以解决什么 问题?

我们知Python中一切皆对象,类也是对象,可以在运行的时候动态创建《当使用关键 宇class的时候,Python解释器在执行的时候就会创建一个对象(这里的对象是指类而非类的实例)。

>>> def dynamic_class(name):
>>>     if name == 'A':
>>>         class A(object):
>>>             pass
>>>         return A
>>>     else:
>>>         class B(object):
>>>             pass
>>>         return B
>>> UserClass = dynamic_class('A')
>>> print(UserClass)
<class '__main__.dynamic_class.<locals>.A'>
>>> UserClass()
<__main__.dynamic_class.<locals>.A at 0x7f162c3eb5f8>

既然类是对象,那么它就有其所届的类型,也一定还有什么东西能够控制它的生成。通 过type査看会发现UserClass的类型为type,而其对象UserClass()的类型为类A。

>>> type(UserClass)
type
>>> type(UserClass())
__main__.dynamic_class.<locals>.A

同时我们知道type还可以这样使用:

type(类名,父类的元组(针对继承的情况,可以为空)r包含属性的字典(名称和值))

例如:

>>> A=type('A',(object,),{'value':2})
>>> A.value
>>> print(A)
<class '__main__.A'>
>>> class C(A):
>>>     pass
>>> print(C)
<class '__main__.C'>
>>> print(C.__class__)
<class 'type'>

上例中type通过接受类的描述作为参数返回一个对象,这个对象可以被继承,属性能够被访问,它实际是一个类,其创建由type控制,由type所创建的对象W_claSS_属性为 type。type实际上是Python的一个内建元类,用来直接指导类的生成。当然,除了使用内建 元类type,用户也可以通过继承type来自定义元类。我们来看一个利用元类实现强制类型检 査的例子。

>>> class TypeSetter(object):
>>>     def __init__(self,fieldtype):
>>>         print ("set attribute type", fieldtype)
>>>         self.fieldtype = fieldtype
>>>     def is_valid(self,value):
>>>         return isinstance (value,self.fieldtype)
>>> class TypeCheckMeta(type):
>>>     def __new__(cls,name,bases, dict):
>>>         print ('----------------------')
>>>         print ("Allocating memory for class", name)
>>>         print (name)
>>>         print (bases)
>>>         print (dict)
>>>         return super(TypeCheckMeta, cls).__new__(cls,name,bases,dict)
>>>     def __init__(cls,name, bases, dict):
>>>         cls._fields = {}
>>>         for key,value in dict.items():
>>>             if isinstance(value,TypeSetter):
>>>                 cls._fields[key]= value
>>> def sayHi(cls):
>>>     print ("Hi")

Typesetter用来设置属性的类型,TypeCheckMeta为用户自定义的元类,覆盖了 type元类 中的__new__()方法和__init__()方法。虽然也可以直接使用TypeCheckMeta(name,bases,diet) 这种方式来创建类,但更为常见的是在需要被生成的类中设置__metaclass__属性,两种用法是等价的。

>>> class TypeCheck(object):
>>>     __metaclass__ = TypeCheckMeta
>>>     def __setattr__(self,key,value):
>>>         print("set attribute value")
>>>         if key in self._fields:
>>>             if not self._fields[key].is_valid(value):
>>>                 raise TypeError('Invalid type for field')
>>>         super(TypeCheck,self).__setattr__(key,value)
>>> class MetaTest(TypeCheck):
>>>     name=TypeSetter(str)
>>>     num = TypeSetter(int)
>>> mt = MetaTest()
>>> mt.name = "apple"
>>> mt.num = "test"
>>>
>>> # 当类中设置了 ``__metaclass__``属性的时候,所有继承自该类的子类都将使用所设置的元 类来指导类的生成,因此上述程序的输出如下:
set attribute type <class 'str'>
set attribute type <class 'int'>
set attribute value
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-16-b4517079954a> in <module>()
     11     num = TypeSetter(int)
     12 mt = MetaTest()
---> 13 mt.name = "apple"
     14 mt.num = "test"
     15


<ipython-input-16-b4517079954a> in __setattr__(self, key, value)
      3     def __setattr__(self,key,value):
      4         print("set attribute value")
----> 5         if key in self._fields:
      6             if not self._fields[key].is_valid(value):
      7                 raise TypeError('Invalid type for field')


AttributeError: 'MetaTest' object has no attribute '_fields'

实际上.在新式类中当一个类未设置__metaclass__属性的时候,它将使用默认的type 元类来生成类。而当该属性被设S时査找规则如下:

1)如果存在dict['__metaclass__'].则使用对应的值来构建类;否则使用其父类dict['__metaclass__']中所指定的元类来构建类,当父类中也不存在指定的metadass的情形下使用默 认元类type。

2)对于古典类,条件1不满足的情况下,如果存在全局变量__metaclass__,则使用该 变量所对应的元类来构建类;齊则使M typcs.ClassType。

读者可以通过将上述例子中__metaclass__=TypeCheckMeta设置为模块级别或者将 TypeCheck改为古典类来验证上述査找规则。

需要额外提醒的是,元类中所定义的方法为其所创建的类的类方法,并不属于该类的对 象。 因此上例中mt.sayHi()会抛出 AttributeError: ‘MetaTest’ object has no attribute ‘sayHi’ 错误, 而调用该方法的正确途径为MetaTest.sayHi()。

那么在什么悄况下会用到元类呢?有句话是这么说的:当你面临一个问题还在纠结要不 要使用元类的时候,往往会有其他的更为简单的解决方案。

Python界的领袖Tim Peters曾这样说过:“元类就是深度的魔法,99%的用户应该根本 不必为此操心,如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到 元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。”

我们来看几个使用元类的场景。

1)利用元类来实现单例模式。

>>> class Singleton(type):
>>>     def __init__(cls, name, bases, dic):
>>>         super(Singleton,cls).__init__(name,bases,dic)
>>>         cls.instance = None
>>> def __call__(cls,*args, **kwargs):
>>>     if cls.instance is None:
>>>         print ("creating & new instance")
>>>         cls.instance = super(Singleton,cls).__call__(*argsr **kwargs)
>>>     else:
>>>         print ("warning:only allowed to create one instance,roinstance already exists!")
>>>     return cls.instance
>>> class NySingleton(object):
>>>     __metaclass__= Singleton

2)第二个例子来源于Python的标准库string,Template,string,它提供简单的字符串替换 功能。常见的使用例子如下:

Template('$name $age').substitute('name':'admin'),age=26)

该标准库的源代码中就用到了元类, Template的元类为_Temp]ateMetadass_TemplateMetadass__init__() 方法通过査找属性(pattern、delimiter 和idpatten)并将其构 建为一个编译好的正则表达式存放在pattern属性中。用户如果需要自定义分隔符(delimiter) 可以通过继承Template并薄盖它的类属性delimiter来实现。string.Template的部分源代码如下:

class Template:

"""A string class for supporting $-substitutions.""" __metaclass__ = _TemplateMetadass delimiter = '$' idpattern = r'[_a-z][_a-z0-9]*'

def __init__(self, template):

self.template = template

class _TemplateMetaClass(type):

pattern = r""" %(delim)s(?: (?P<escaped>%(delim)s) | # Escape sequence of two delimiters (?P<named>%(id)s) | # delimiter and a Python identifier (?P<braced>%(id)s) | # delimiter and a braced identifier (?P<invalid>) | # Other ill-formed delimiter exprs

)"""

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

super(_TemplateMetaclass, cls).__init__(name, bases, dct) if 'pattern' in dct:

pattern = cls.pattern

else:
pattern = _TemplateMetaclass.pattern %(

'delim': _re.escape(cls.delimiter),

'id': cls.idpattern, )

cls.pattern = _re.compile(pattern, _re.IGNORECASE | _re.VERBOSE)

另外在Django ORM、AOP编程中也有大量使用元类的惰形。最后来谈谈关于元类需要注意的几点:

1)区别类方法与元方法(定义在元类中的方法>。我们先来看一个例子:Meta和 SubMeta都为元类,其中SubMeta继承自Meta。因此f1、f2都为元方法,而Test为普通类, 其元类设置为SubMeta, f3为类方法。

>>> class Meta(type):
>>>     def f1(cls):
>>>         print("This is f1()")
>>> class SubMeta(Meta):
>>>     def f2(cls):
>>>         print("This is f2()")
>>> class Test(object):
>>>     __metaclass__ = SubMeta
>>>     @classmethod
>>>     def f3(cls):
>>>         print("I am f3()")
>>> t = Test()
>>> SubMeta.f1(Test)
This is f1()
>>> Meta.f1(Test)
This is f1()
>>> SubMeta.f2(Test)
This is f2()
>>> Test.f2()
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-7-f2c5def10653> in <module>()
----> 1 Test.f2()


AttributeError: type object 'Test' has no attribute 'f2'
>>> t.f2()
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-8-d017aacf0268> in <module>()
----> 1 t.f2()


AttributeError: 'Test' object has no attribute 'f2'
>>> Test.f3()
I am f3()
>>> t.f3()
I am f3()

上面的例子说明.元方法可以从元类或者类中调用,而不能从类的实例中调用;怛类方 法可以从类中调用,也可以从类的实例中调用。

2)多继承需要严格限制,否则会产生冲突。

>>> class M1(type):
>>>     def __new__(meta,name,bases,atts):
>>>         print("M1 called for" + name)
>>>         return super(M1,meta).__new__(meta,name,bases,atts)
>>> class C1(object):
>>>     __metaclass__ =M1
>>> class Sub1(C1):pass
>>> class M2(type):
>>>     def __new__(meta,name,bases,atts):
>>>         print("M2 called for " + name)
>>>         return super(M2,meta).__new__(meta,name,bases,atts)
>>> class C2(object):
>>>     __metaclass__ = M2
>>>
>>> class Sub2(C1,C2):
>>>     pass

上面的例子中当Sub2同时继承自元类Cl和C2的时候会抛出异常,这是因为Python解 释器并不知道C1和C2是否兼容,因此会发出冲突督告=解决冲突的办法是重新定义一个派 生自Ml和M2的元类,并在C3中将其__metadass__属性设置为该派生类。

>>> class M3(M1,M2):
>>>     def __new__(meta,name,bases,atts):
>>>         print("M3 called for" + name)
>>>         return super(M3,meta).__new__(meta,name,bases,atts)
>>> class C3(C1,C2):
>>>     __metaclass__ = M3
>>>

注意:元类用来指导类的生成,元方法可以从元类或者类中调用,不能从类的实例中调用, 而类方法既可以从类中调用也可以从类的实例中调用。