什么是元类
在python中,想必__init__,大家基本都知道,然后再有一部分人知道__new__,再到元类,知道的就更少了些了。那么,什么是元类🤔
元类,官方的定义是,类的类型。而类型的顶点,便是type。
metaclass - The class of a class. Class definitions create a class name, a
class dictionary, and a list of base classes. The metaclass is responsible for
taking those three arguments and creating the class. Most object oriented programming languages provide a default implementation. What makes
Python special is that it is possible to create custom metaclasses. Most users
never need this tool, but when the need arises, metaclasses can provide
powerful, elegant solutions. They have been used for logging attribute
access, adding thread-safety, tracking object creation, implementing
singletons, and many other tasks.
上面的内容大致写的是:元类,是类的类型。然后定义了name(类的名称),dict(可用于传入一些内置参数),bases(包含所有父类的列表)。然后猿类可以根据些参数创建一个类。然后就是一些元类的用途,用于记录属性的获取、确保线程安全、跟踪对象的创建、实现单例模式,等等。
实例分析
父子关系分析
仅仅通过描述,似乎不是很好理解。于是整了些小实验:效果如下:
>>> object.__base__
>>> type.__base__
<class 'object'>
>>> type.__class__
<class 'type'>
>>> object.__class__
<class 'type'>
先看父子关系,我们可以看到:
object没有父类,而type的父类是object。
因此,我们可以得出object是父类的顶点。
再看类型,同理:
object的类型是type,type的类型也是type。
因此,type是类型的顶点。但是,为什么object的类型也是type呢。所以我们的结论是,type是基于object实现的,而基于object实现的type实现的type被定义为了类型的顶点。
元类的用途
动态且快速地创建一个类
>>> t = type("test",(),{"a":0})
>>> t.a
0
>>> t
<class '__main__.test'>
追踪对象的创建
当然,在追踪的同时,我们也可以做一些事情,比如对属性的大小写做限制。
class UpperAttrMetaclass(type):
def __new__(cls, name, bases, dct):
uppercase_attrs = {}
for attr_name, attr_value in dct.items():
if not attr_name.startswith('__'):
uppercase_attrs[attr_name.upper()] = attr_value
else:
uppercase_attrs[attr_name] = attr_value
# 使用修改后的属性创建新的类
return super().__new__(cls, name, bases, uppercase_attrs)
# 使用 UpperAttrMetaclass 元类创建一个新类
class MyClass(metaclass=UpperAttrMetaclass):
my_attr = "hello"
# 测试 MyClass 中的属性名是否已转换为大写
obj = MyClass()
print(obj.MY_ATTR) # 输出: hello
经过测试发现,new__函数的的cls对象变量创建会经过以下逻辑:
1、当前类有__metaclass__这个属性吗?如果是,Python会在内存中通过__metaclass__创建一个名字为当前类的main函数类对象)
2、如果Python没有找到__metaclass,它会继续在父类中寻找__metaclass__属性,并尝试做和前面同样的操作
3、如果Python在任何父类中都找不到__metaclass__,它就会在模块层次中去寻找__metaclass__,并尝试做同样的操作
4、如果还是找不到__metaclass__,Python就会用内置的type来创建这个类对象。
这也就是为什么,我们在用type不传参时,与不传metaclass参数时会得到类似的报错信息。判断type类的__call__逻辑是三个参数时,调用__new__函数创建实例。这也就符合了,我们上方的逻辑,type所有类型的顶点,所有的实例都是基于type的实例创建的。
实现单例模式(本质上利用了闭包思想)
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class MyClass(metaclass=Singleton):
pass
obj1 = MyClass()
obj2 = MyClass()
print(obj1 is obj2) # 输出: True
实际案例
Django框架中的ORM(对象关系映射)
Django使用元类来创建数据库模型类和数据库表之间的映射关系。通过定义模型类并使用特定的元类,Django能够自动创建数据库表结构,以及提供方便的API来进行数据库操作。
类型检查和验证
一些类型检查工具和框架使用元类来验证代码中的类型注解和类型约束。通过拦截类的创建过程,元类可以检查类的属性和方法是否符合类型注解的要求,并提供类型安全性。
自动化API绑定
某些框架和工具使用元类来自动生成API绑定代码。通过定义特定的元类,可以在运行时自动创建API绑定,并提供便捷的接口来访问和调用函数和方法。
插件系统
元类可用于创建插件系统,允许开发人员针对现有类进行扩展或修改。通过定义一个元类,可以在运行时将插件应用到类上,并在类定义时自动应用插件的功能。
序列化和反序列化
某些序列化库使用元类来自动地将对象转换为序列化格式(如JSON、XML等),以及将序列化数据反序列化为对象。通过定义特定的元类,可以控制对象的序列化和反序列化过程。