【python】进阶之元类(六)

元类【深入理解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) # Student实例化得到对象 stu
print(type(stu)) # type()函数可以得到 stu这个对象的类是Student
print(stu.__class__) # 类似的方式 得到 stu这个对象的类是Student


print(type(Student)) # 类似的 通过type()的方式得到Student的类型是 type
print(Student.__class__) # 类似的通过 __class__ 得到Student的类是 type


# 于是得到结论 类也是对象,类的类是type, 即type实例化得到的对象是类

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) # 自定义了元类信息,Student是实例化对象

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):	# class机制默认的元类是type:我们可以修改metaclass参数来选择自定义元类
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):		# 只有继承了type的类才能作为元类使用
# 在这个里面去重写type的方法去自定义自己的元类
pass


class Student(metaclass=Mymeta): # 使用Mytema元类,即Mymeta(class_name, class_bases, class_dict)
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(): # 自定制需求 istitle()用于检查字符串的首字母是否大写,如果大写就是Ture,否则Flase
raise NameError('类名首字母必须大写')

if not self.__doc__: # # 如果类的__doc__属性是空,就抛出异常 doc就是注释那里
raise TypeError('类必须要有文档注释')

super().__init__(class_name, class_bases, class_dict)


# People = Mymeta(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):
# 第一步: 调用Student下面的__new__创建一个空对象, 无则使用类(type)的__new__
obj = cls.__new__(cls, *args, **kwargs)
# 第二步:调用Student下面的__init__初始化对象
cls.__init__(obj, *args, **kwargs) # 等价于: obj.__init__(*args, **kwargs)
# 第三步:返回对象
return obj


class Student(object, metaclass=Mymeta):
def __init__(self, name, age):
self.name = name
self.age = age


jack = Student('jack', 18) # People.__call__('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 # 默认类的属性_instance是空
def __new__(cls,*args, **kwargs):
self.name = name
self.age = age

def __new__(cls,*args, **kwargs):
if cls._instance: # 判断,如果cls._instance is Ture就是指判断他是否有实例
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): # 此call会在类被调用(即实例化时出发)
if cls._instance: # 判断类有没有实例化对象
return cls._instance # 如果有就返回这个实例化对象啊
else: # 如果没有
obj = cls.__new__(cls, *args, **kwargs) # 那就控制类造一个空对象并完成初始化
obj.__init__(*args,**kwargs) # 那就控制类造一个空对象并完成初始化
cls._instance = obj # 保存这个对象,那cls的_instance是这个对象,下一次再实例化的时候就直接返回而不用再造对象
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–方法吧? 这个时候你就可以重写(自定义)元类去实现呀,。这样你每个定义类只需要继承自定义元类就行。

拓展一下,场景:

  1. 验证和数据处理: 你可以通过自定义元类来验证类的属性和方法是否符合某些规则。例如,你可以创建一个元类,确保所有的属性都是特定类型,或者属性的值满足某些条件。
  2. 日志和调试: 自定义元类可以用来自动添加日志、跟踪或调试信息到类的方法中,从而帮助你更好地理解代码的执行过程。
  3. ORM(对象关系映射): 自定义元类可以用于创建 ORM 框架中的数据库模型,将 Python 类映射到数据库表,从而实现数据的存储和检索。
  4. API 定义: 通过自定义元类,你可以创建领域特定语言(DSL)的类,从而提供更高级别的 API 接口,以适应特定的业务需求。
  5. 依赖注入: 自定义元类可以用于自动注入依赖项,从而简化类的构造过程。
  6. 单元测试辅助: 自定义元类可以用于在测试中自动生成 Mock 对象,帮助进行单元测试。
  7. 实现属性访问控制: 通过自定义元类,你可以控制类的属性访问权限,从而实现公开、私有和受保护的属性。
  8. 插件系统: 自定义元类可以用于创建可插拔的插件系统,允许你动态地扩展类的功能。

日志和调试:

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方法,在里面做判断,如果类你定义的类的属性符合某个要求,就没事,如果不如何对应的要求,就会抛出异常报错
  • 然后你在定义类的时候,指定元类

【python】进阶之元类(六)
http://example.com/2024/03/20/606python进阶之元类(六)/
作者
Wangxiaowang
发布于
2024年3月20日
许可协议