4.9 元类metaclass(type类)及object类
4.9.1 object类
在面向对象编程中,类是对象的蓝图或模板。类定义了一组属性和方法,并且根据该模板可以创建新的对象。由于每个对象都是基于类来创建的,因此它们共享相同的属性和方法。
object类是一个非常重要的类,它是所有类的基类。在之前的Python版本中,如果我们需要创建自己的类,需要手动继承object类。
在Python中,几乎所有的对象都可以使用它的方法和属性,包括整数、字符串、元组等内置类型的对象。例如:
x = 5
print(x.__class__) # <class 'int'>
print(x.__dir__()) # [......'bit_length', 'to_bytes', 'hex'......]
这里我们使用__class__()魔术方法打印变量所属类,调用__dir__()魔术方法打印变量的所有可用属性和方法。
4.9.2 元类metaclass(type类)
在 Python 中,元类是用来创建其他类的类。简单说,它可以看作是类的工厂,控制着类的创建过程。元类最常见的使用场景是对类进行定制化处理,比如动态添加属性或方法等。
class type是所有类的元类(metaclass)。元类是类的“类”。它们允许定义新的类,就像类定义实例对象一样。使用元类,可以控制如何创建一个类。只要派生自class type的类都是元类,并且type是默认元类。
在Python中,有2种方式定义普通类:静态定义
和动态生成
。
from icecream import ic
class A(int): # 静态定义
aa = 1
B = type('C', (int,), {'bb': 2}) # 动态生成
ic(type(A), type(A()))
ic(type(B), type(B()))
ic(A.aa, A().aa)
ic(B.bb, B().bb)
14:10:06|> type(A): <class ‘type’>, type(A()): <class ‘main.A’>
14:10:06|> type(B): <class ‘type’>, type(B()): <class ‘main.C’>
14:10:06|> A.aa: 1, A().aa: 1
14:10:06|> B.bb: 2, B().bb: 2
我们看到,使用class定义类其实就是通过调用type函数进行定义。两者几乎等价。
from icecream import ic
class M(type):
def __new__(mcs, *args, **kwargs):
print(mcs, args, kwargs)
return type.__new__(mcs, *args, **kwargs)
class A(metaclass=M): # 相当于A = M('A', (), {}),而M则为A的元类
pass
<class ‘main.M’> (‘A’, (), {‘module’: ‘main’, ‘qualname’: ‘A’}) {}
如上定义class M,M是A的元类,A为M的object。而a又是A的object。在通过M创建A的时候,此时会依次运行M的__new__()方法和__init__()方法。
在一般的类中,是只会运行__new__()方法的,除非没有__new__()方法,则会运行__init__()方法。
这个时候我们可以通过重写M中的__new__()方法和__init__()方法来做一些自定义操作。
在metaclass里定义的__init__()方法,这个方法是在通过元类创建出类后调用的。
from icecream import ic
import random
class M(type):
def __new__(mcs, *args, **kwargs):
print(mcs, args, kwargs)
return type.__new__(mcs, *args, **kwargs)
def __init__(cls, *args, **kwargs):
print(cls, args, kwargs)
cls.random_id = random.randint(0, 100)
return type.__init__(cls, *args, **kwargs)
class A(metaclass=M):
pass
ic(A.random_id)
<class ‘main.M’> (‘A’, (), {‘module’: ‘main’, ‘qualname’: ‘A’}) {}
<class ‘main.A’> (‘A’, (), {‘module’: ‘main’, ‘qualname’: ‘A’}) {}
15:38:07|> A.random_id: 27
在metaclass里还可以继续定义__call__()方法,这个方法是在通过元类创建出类后,再通过该类实例化时调用的。
注意:普通的类中,在通过该类的实例化对象调用后才会运行__call__()方法。
class M(type):
def __new__(mcs, *args, **kwargs):
print(mcs, args, kwargs)
return type.__new__(mcs, *args, **kwargs)
def __call__(cls, *args, **kwargs):
print('call')
return type.__call__(cls, *args, **kwargs)
class A(metaclass=M):
pass
a = A()
<class ‘main.M’> (‘A’, (), {‘module’: ‘main’, ‘qualname’: ‘A’}) {}
call
元类一般用来解决继承没办法处理的问题。可以让你更灵活的掌握建立类中间的逻辑,比如我们可以通过class里面定义的成员来增加或删除方法。
实例一:禁止自定义类中出现‘test_’开头的方法
from icecream import ic
class M(type):
def __new__(mcs, *args, **kwargs):
print(mcs, args, kwargs)
for key in args[2]:
if key.startswith('test_'):
raise ValueError()
return type.__new__(mcs, *args, **kwargs)
class A(metaclass=M):
def test_case(self):
pass
<class ‘main.M’> (‘A’, (), {‘module’: ‘main’, ‘qualname’: ‘A’, ‘test_case’: <function A.test_case at 0x000001729106CD30>}) {}
Traceback (most recent call last):
File “E:/t3.py”, line 13, in
class A(metaclass=M):
File “E:/BaiduNetdiskWorkspace/FrbPythonFiles/Project/t3.py”, line 9, in new
raise ValueError()
ValueError
示例二:通过元类创建的类具有某些属性
from icecream import ic
class MyMeta(type):
def __new__(cls, name, bases, attrs):
attrs["author"] = "China"
attrs["version"] = (1, 0)
ic(cls, name, bases)
return super().__new__(cls, name, bases, attrs)
class MyClass(metaclass=MyMeta):
pass
print(MyClass.author) # China
print(MyClass.version) # (1, 0)
China
(1, 0)
16:07:04|> cls: <class ‘main.MyMeta’>, name: ‘MyClass’, bases: ()
在这个例子中,MyMeta 是一个元类,它继承自 type 类。在 MyMeta 中重写了 new 方法,在创建类时会被调用。在 new 方法中,将作者和版本号添加到传入的 attrs 参数中。
MyClass 是使用 MyMeta 元类创建的一个类,在这个类定义中,通过 metaclass 参数指定了元类。在程序执行时,MyClass 类会被创建,并且在创建时会先调用 MyMeta 的 new 方法,然后再返回一个新的类对象。最终,MyClass 对象的 author 和 version 属性会分别是 “China” 和 (1, 0)。
实例三:通过元类实现单例
class Singleton(type):
_instance = None
def __call__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__call__(*args, **kwargs)
return cls._instance
class MyClass(metaclass=Singleton):
pass
a = MyClass()
b = MyClass()
print(a is b) # True
这段 Python 代码实现了一个单例模式,确保在整个程序中只存在一个类的实例。
首先定义了一个 Singleton 类型,这个类实现了构造方法 call(),通过 cls._instance 变量判断当前是否已有一个实例存在,如果没有,则创建一个新的实例并将其存储在 cls._instance 变量中;如果已经有一个实例存在,则直接返回该实例。
然后我们定义另一个类 MyClass,并将它的 metaclass 设置为 Singleton,这样 MyClass 就变成了一个单例类,调用 a = MyClass() 创建的对象与 b = MyClass() 创建的对象实际上是同一个实例。
最后输出 a is b 的结果,由于 a 和 b 实际上都指向了同一个实例,因此结果为 True。
4.9.3 type类和object类的关系
元类type规定了object这个所有类的基类应该长什么样子,按照元类type的模子产生的实例object自然也就成了一个类。但是type本身也是一个类,而且object先于type,因为’help(object)‘的执行结果第一行是’class object’,object就不继承任何类了;而’help(type)‘的执行结果第一行是’class type(object)’,说明type继承object,它虽然是类的类,但‘类’这个身份也恰恰让它不得不符合类的基本形式。type、object、instance关系如下:
由此可见,type和object的关系可以总结为:
1、所有类都继承object(除object外)
2、所有类都是type的实例(包括type自己)