元类介绍
什么是元类,简单来说元类其实就是创建类的类,也就是以类作为其实例的类。
实例对象是类被调用也就是实例化的产物,而类又是如何被调用产生的呢?
其实默认情况下,类是使用 type() 来构建的,type 是 python 的一个内建元类,用来直接控制生成类
我们知道 type(object) 这个方法可以让我们返回对象的类型
示例:
class MyCls(object):
pass
x = MyCls()
print(type(666)) # 打印结果: <class 'int'>
print(type(x)) # 打印结果: <class '__main__.MyCls'>
整数 666 的类型是 int 。实例对象 x 的类型是示例中的 MyCls 类,如果我们对 MyCls 类以及 int 使
用 type() 方法会出现什么?
示例:
class MyCls(object):
pass
print(type(int)) # 打印结果: <class 'type'>
print(type(MyCls)) # 打印结果: <class 'type'>
可以看到这俩个的类型都是 'type' , 其实 print(type(type)) 的结果也是 type 。
所以我们大概也能知道 type 就是元类
type()
type() 的语法
type(object) -> the object's type
type(name, bases, dict, **kwds) -> a new type
第二个语法 type(name, bases, dict, **kwds) ---> type(类名,父类,类的名称空间)
name 字符串即类名并会成为 __name__ 属性;
bases 元组包含基类并会成为 __bases__ 属性;如果为空则会添加所有类的基类 object。
dict 字典包含类主体的属性和方法定义
创建类的形式
type(name, bases, dict, **kwds) 语法也可以创建类。所以加上class语法创建类的形式总共有俩种
代码示例
class MyCls:
a = 1
print(MyCls)
x = type('MyCls', (), {})
print(x)
打印结果
<class '__main__.MyCls'>
<class '__main__.MyCls'>
也就是说这俩种形式都能创建类。
由于产生类的是 type 所以 class 关键字其实是执行了 type(name, bases, dict) 。
元类的基本使用
元类能够控制类的创建和修改,所以也就意味着我们可以高度定制类的行为
前面写的一个类在默认情况下它的元类都是 type 。但是我们如果要控制类的创建和修改就需要用到继承和
重写我们可以定义一个类继承元类 type 并进行修改,然后让它成为新的元类
metaclass
类创建过程可通过在定义行传入 metaclass 关键字参数,或是通过继承一个包含此参数的现有类来进行定制。并不能直接通过继承的方式来继承一个重写了元类的类。
语法格式
class Meta(type):
pass
class MyClass(metaclass=Meta):
pass
class MySubclass(MyClass):
pass
代码示例
class MyType(type):
def __init__(cls, name, bases, dict):
print(name, bases, dict)
super().__init__(name, bases, dict)
class C1(metaclass=MyType):
pass
x = C1()
打印结果
C1 () {'__module__': '__main__', '__qualname__': 'C1'}
示例中是简易的形式,首先定义 MyType 类继承元类 type ,并在其中定义 __init__ 方法进行修改并
用 super() 重新接收元类的方法。在定义一个类用关键字 metaclass 来与类 MyType 建立联系。此时就
相当于类 C1 不再是默认元类 type 了而是类 MyType。
我们可以在其中添加判断条件
代码示例
class MyType(type):
def __init__(cls, name, bases, dict):
if not name.istitle():
raise Exception('类名首字母必须大写')
super().__init__(name, bases, dict)
class c1(metaclass=MyType): # 抛出异常
pass
x = c1()
在示例中加了一个条件,创建类的时候类名首字母必须大写,不然抛出异常。
元类进阶用法
这里回想 __call__
方法,该方法在写在类里时会在实例对象被加括号调用的时候执行且返回的值等同于 实例对象()
。我们可以推导一下,如果写在继承了元类 type 的类中是不是在类名加括号调用的时候执行?返回的值是不是类名()
?
代码示例
class MyType(type): # 定义继承元类type的类
def __call__(self, *args, **kwargs): # 定义方法__call__
print('from MyType.__call__')
return super().__call__(*args, **kwargs)
class C1(metaclass=MyType): # 继承了被修改的元类
def __init__(self): # 定义__init__ 方法, __init__ 不需要 return 返回
print('from C1.__init__')
x = C1()
打印结果
from MyType.__call__
from C1.__init__
根据打印结果来看,首先执行的是 MyType 中的 __call__ 方法,在执行类 C1 中的 __init__ 方法。
即使 __init__ 方法在类被实例化的时候就自动执行。
我们可以添加一些条件
代码示例
class MyType(type):
def __call__(self, *args, **kwargs):
if args:
raise Exception('只能传入关键字形式')
return super().__call__(*args, **kwargs)
class C1(metaclass=MyType):
def __init__(self, name):
self.name = name
x = C1(name='xxx')
上面的示例的作用是强制规定类在实例化产生对象的时候,对象的独有数据必须采用关键字参数传入。
编写元类里面的__init__方法可以高度定制类的产生过程
编写元类里面的__call__方法可以高度定制对象的产生过程
__new__ (用于产生空对象)
__new__
方法是一个可以创建类实例的静态方法。该方法会优先于__init__
被执行
如果是在元类的__new__里面 可以直接调用,如果是在元类的__call__里面 需要间接调用
代码示例一
class MyType(type):
def __new__(cls, *args, **kwargs):
obj = type.__new__(cls, *args, **kwargs)
return obj
class C1(metaclass=MyType):
pass
x = C1()
print(x)
代码示例二
class MyType(type):
def __call__(self, *args, **kwargs):
obj = object.__new__(self) # 创建一个空对象
self.__init__(obj, *args, **kwargs) # 让对象去初始化
return obj
class C1(metaclass=MyType):
pass
x = C1()
print(x)