>>> 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
>>>
注意:元类用来指导类的生成,元方法可以从元类或者类中调用,不能从类的实例中调用, 而类方法既可以从类中调用也可以从类的实例中调用。