元类【深入理解Python】
6.1 类也是对象
- 元类是什么?要先了解类是什么?
- Python是一切皆对象,哪怕你定义一个变量,它也是对象,那类是什么? 类可以想象成是对象的模版,是类定义了对象,对象是类实例化的产物,
- 那类是对象的模板,什么是类的类(模版)呢?
- 元类就是类的类(模版)默认元类type
- 你可以自定义这个元类来实现有区别于类的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Student: def __init__(self, name, age): self.name = name self.age = age
stu = Student("jack", 18) print(type(stu)) print(stu.__class__)
print(type(Student)) print(Student.__class__)
|
6.2 class机制
1 2 3 4 5 6 7 8 9 10 11 12
| class Student: def __init__(self, name, age): self.name = name self.age = age def talk(self): print('hello')
- 类名:Student - 基类们:object, - 名称空间:就是类下面对象的属性和方法,构成的一个字典 key是属性/方法名字符串,value是属性/方法本身
|
class是python的一个关键字,目的是用来创建类,那这个关键字的背后是什么逻辑呢?
- 第一步:获取类名 class_name = Student
- 第二步: 获取基类们: class_bases=(object,)
- 第三步:获取类的名称空间: class_dict = {“–init–” :–init–,”–talk–”:talk}
- 第四步:调用元类type实例化产生Student类这个对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| def __init__(self, name, age): self.name = name self.age = age
def talk(self): print('hello')
class_name = "Student" class_bases = (object, ) class_dict = {"__init__": __init__, "talk": talk}
Student = type(class_name, class_bases, class_dict)
stu = Student("jack", 18) print(stu.talk())
|
- 但是如果按照这四步走就比较繁琐,所以正常是用python的关键字class 来定义类的
6.3 自定义元类
上面我们知道了class的原理,那我们自定义元类,就是在做第四步的自定义,即使用不同的元类实例化这个对象,目的是为了按照需求控制类的定义和调用
metaclass=type就是让他指向type元类,这是所有类的基石
1 2 3 4 5 6 7
| class Student(metaclass=type): def __init__(self, name, age): self.name = name self.age = age def talk(self): print('hello')
|
- type是一切类的基石,所以我们自定义的元类,也必须继承自
type
- 自定义的元类继承type的目的是使用type的大部分功能,我们只定制(重写方法)我们需要的那一部分功能
1 2 3 4 5 6 7 8 9 10 11 12
| class Mymeta(type): pass
class Student(metaclass=Mymeta): def __init__(self, name, age): self.name = name self.age = age def talk(self): print('hello')
|
6.4 自定义元类控制类的定义
自定义元类定制类的时候,需要再自定义的元类中实现init()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Mymeta(type): def __init__(self, class_name, class_bases, class_dict):
if not class_name.istitle(): raise NameError('类名首字母必须大写') if not self.__doc__: raise TypeError('类必须要有文档注释')
super().__init__(class_name, class_bases, class_dict)
class People(object, metaclass=Mymeta): """ deox """ x = 10 def f(self): pass
print(People.__dict__)
|
6.5实例化对象的本质是什么?
回顾一下,实例化一个对象时会发生什么?
- 第一件事:创建了一个空对象,–new–方法
- 第二件事:初始化了这个空对象 –init–
- 最后:返回初始化完成的对象
- new()创建空对象,且返回这个空对象
- init() 接收了这个空对象,并完成了初始化该对象
- 最后,返回初始化完成的对象。
1 2 3 4 5
| class Student: def __init__(self, name, age): self.name = name stu = Student('jack', 18)
|
整个三步流程,是由类的类,即元类中的–call–()方法管理的(定义.类() ),因为实例化对象,类加括号,即类的调用。类被当一个对象看待时,直接以类方法调用,就会触发它的–call–()函数的执行,即实例化对象时,出发了元类的–call–()函数
在元类–call–()内实现实例化的三件事。
当我们默认使用的是type元类时,想要定制实例化的这个过程中的需求是无法实现的,因为我们无法修改内置元类type的–call–方法。
当我们使用自定义元类的时候,就可以实现实例化过程需求的自定义,因为我们可以重写自定义元类的–call–方法,从而实现自定义实例化需求
6.6 自定义元类控制类的调用
类的调用: 就是类加括号,也就是实例化对象
你实例化一个类的对象时,实际上会调用类的 __call__
方法(如果定义了的话)。这种行为使得类的实例可以像函数一样被调用。
控制类的调用就是控制对象的额实例化过程。类的调用就会触发元类的__call__
方法执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Mymeta(type): def __call__(cls, *args, **kwargs): obj = cls.__new__(cls, *args, **kwargs) cls.__init__(obj, *args, **kwargs) return obj
class Student(object, metaclass=Mymeta): def __init__(self, name, age): self.name = name self.age = age
jack = Student('jack', 18)
|
6.7 通过new实现单例模式
回顾一下单例模式:
- 一个类只实例化一个对象的一种编程思维,就是单例模式
- 怎么实现: 通过自定义–new–()魔法方法实现,判断类的实例化对象是否已经有了,有了就直接返回这个实例化对象,没有就可以new一个实例化并保存返回。看看对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Student(): _instance = None def __new__(cls,*args, **kwargs): self.name = name self.age = age def __new__(cls,*args, **kwargs): if cls._instance: return cls._instance else: cls._instance = super().__new__(cls) return cls._instance def __new__(cls, *agrs, **kwagrs): if not cls._instance: cls._instances = super().__new__(cls) return cls.instances
|
6.8 通过元类来实现单例模式【重点】
思路是什么?
- 首先,这个实现的点在哪里?在类的定义阶段就确保类的实例化行为符合单例模式,
- 呐你单例模式,是在定义类的时候做判断,通过类的–new–()方法里面去做判断,如果这个类有实例化对象,就返回这个对象,如果没有就新建一个对象返回,
- 这里插一句,实例化的过程发生了什么?1、–new–新建一个空对象且返回出去给–init– 2、–init–接收这个空对象,进行初始化,就是slef.name=name,3、将这个初始化过的对象返回去。就是调用类的‘爸爸’’元类的–call–方法,–call–就是.类()把类当方法用的方法
- 好的,现在我们知道了实例化过程,那我们除了可以在new方法这里做判断,还可以在定义类的‘‘爸爸’’元类的时候去做判断
- 因为你在实例化的时候,就是调用了类的‘‘爸爸’’元类的–call–方法去实例化的(–call–把类当方法用)
- 好,现在我们知道了要去自定义元类的–call–方法,也就是在调用–call–的时候去做判断,判断这个类有没有实例化对象,如果有就直接返回这个对象,如果没有就新建一个对象然后再返回这个对象。
- 好我现在要再自定义的原来上面去做判断是吧,那怎么自定义元类?且看代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Mymeta(type): def __init__(cls, name,bases,dic): super().__init__(name,bases,dic) cls._instance = None def __call__(cls,*args, **kwargs): if cls._instance: return cls._instance else: obj = cls.__new__(cls, *args, **kwargs) obj.__init__(*args,**kwargs) cls._instance = obj return obj class Student(metaclass=Mymeta): def __init__(self,name,age): self.name=name self.age=age stu1 = Student("jone",18) stu2 = Student('xiaoming',19) print(stu1 is stu2) print(stu1.__dict__,stu2.__dict__)
|
原理解读:类定义时会调用元类下面的–init–(),类调用(实例化对象)时会触发元类下的–call–方法
类在定义时,给类新增一个空的数据属性,
第一次实例化的时候,实例化之后就会将这个对象赋值给类的数据属性; # 所以第一次实例化的时候,就获得了这个属性
第二次再实例化的时候,直接返回类的这个数据属性
和自定类的时候单例不同的是,类的这1个数据属性是放在元类中自动定义的,而不是在类中显示的定义的
类调用时,出发元类–call–方法判断是否有实例化对象,而不是在类的绑定方法中做判断。
最后我说一句,其实两个的原理是一样的,
- 你使用定义类的方式:你在类中做了判断,其实就是你是在实例化对象的‘‘爸爸’’定义类中的–new–也就是新建对象的时候做的判断,
- 你使用自定义元类的方式,也就是你在定义类的‘‘爸爸’’元类的–call–方法做的判断,因为类实例化对象就是调用的元类的–call–方法
- 那二者的区别?
- 你全局只有一个类的单例,那你就在定义类的时候做下判断就好,那如果你的程序要定义好几个只能单例的类的?不可能每个类你都去重写–new–方法吧? 这个时候你就可以重写(自定义)元类去实现呀,。这样你每个定义类只需要继承自定义元类就行。
拓展一下,场景:
- 验证和数据处理: 你可以通过自定义元类来验证类的属性和方法是否符合某些规则。例如,你可以创建一个元类,确保所有的属性都是特定类型,或者属性的值满足某些条件。
- 日志和调试: 自定义元类可以用来自动添加日志、跟踪或调试信息到类的方法中,从而帮助你更好地理解代码的执行过程。
- ORM(对象关系映射): 自定义元类可以用于创建 ORM 框架中的数据库模型,将 Python 类映射到数据库表,从而实现数据的存储和检索。
- API 定义: 通过自定义元类,你可以创建领域特定语言(DSL)的类,从而提供更高级别的 API 接口,以适应特定的业务需求。
- 依赖注入: 自定义元类可以用于自动注入依赖项,从而简化类的构造过程。
- 单元测试辅助: 自定义元类可以用于在测试中自动生成 Mock 对象,帮助进行单元测试。
- 实现属性访问控制: 通过自定义元类,你可以控制类的属性访问权限,从而实现公开、私有和受保护的属性。
- 插件系统: 自定义元类可以用于创建可插拔的插件系统,允许你动态地扩展类的功能。
日志和调试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class DebugMeta(type): def __new__(cls, name, bases, cls_dict): for attr_name, attr_value in cls_dict.items(): if callable(attr_value): cls_dict[attr_name] = cls.debug_decorator(attr_value) return super().__new__(cls, name, bases, cls_dict)
@staticmethod def debug_decorator(func): def wrapper(*args, **kwargs): print(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}") result = func(*args, **kwargs) print(f"{func.__name__} returned: {result}") return result return wrapper
class DebugClass(metaclass=DebugMeta): def add(self, a, b): return a + b
def subtract(self, a, b): return a - b
debug_instance = DebugClass() result = debug_instance.add(5, 3) print(result)
|
- 自定义元类的new方法,
- 自定义一个装饰器,就是被装饰的方法运行前打印方法名称和入参之类的,方法结束的时候打印一下return值
- 在new方法里写,遍历后判断类的属性是否可调用 if callable(属性名):如果可以调用就使用装饰器装饰一下这个方法 打印日志
- 然后再定义类的时候继承这个自定义元类。
验证和数据处理
- 就是自定义元类new方法,在里面做判断,如果类你定义的类的属性符合某个要求,就没事,如果不如何对应的要求,就会抛出异常报错
- 然后你在定义类的时候,指定元类