Python进阶之元类
【一】什么是元类
- Python中一切皆对象,八大基本数据类型是对象,类实例化得到的对象也是对象,其实类本身也是一种对象
- 元类是所有类的基类,包括object
class Student(object):
def __init__(self, name):
self.name = name
student = Student('ligo')
# 类的数据类型
print(type(Student)) # <class 'type'>
# 实例化对象的数据类型
print(type(student)) # <class '__main__.Student'>
# 字典的数据类型
print(type(dict)) # <class 'type'>
# object的数据类型
print(type(object)) # <class 'type'>
【二】为什么要使用元类
- 元类可以控制类的创建,也就意味着我们可以高度定制类的具体行为
- 比如,当我们掌控了食品的生产过程,我们就可以在里面随便动手脚
【三】元类的两种创建方式
【1】直接关键字创建
- 就是正常创建类的方式:
class 类名(继承的父类) #默认是object:
# 类体代码
class Student(object):
def __init__(self, name):
self.name = name
student = Student('ligo')
# 类的数据类型
print(type(Student)) # <class 'type'>
# 类的名称空间
print(Student.__dict__)
# {'__module__': '__main__', '__init__': <function Student.__init__ at 0x000001B10151A7A0>, '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None}
【2】通过type创建
类名 = type('类名',(父类1,父类2,...),名称空间字典)
class Student(object):
school = '北京大学'
def __init__(self, name):
self.name = name
def read(self):
print(f"学生{self.name}的学校是{self.school}")
Student = type('Student', (object,), {'name': "ligo", 'read': read})
# 类的数据类型
print(type(Student)) # <class 'type'>
# 类的名称空间
print(Student.__dict__)
# {'name': 'ligo', 'read': <function read at 0x00000279DC0E3E20>, '__module__': '__main__', '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None}
# 我们使用关键字创建类内的方法的时候,查看他的内存空间是带着类名字的
# 而我们当前创建的这个类,初始化进去的只是一个普普通通的函数,也就是非绑定方法
【四】元类的基本使用
- 要求所有类的名字必须是大写的
【1】基本使用
# 创建元类
class MyType(type):
def __init__(cls, class_name, class_bases, class_dict):
print("当前类的本身:", cls)
# 当前类的本身: <class '__main__.MyClass'>
print("当前类的类名:", class_name)
# 当前类的类名: MyClass
print("当前类的父类:", class_bases)
# 当前类的父类: (<class 'object'>, )
print("当前类的名称空间:", class_dict)
# 当前类的名称空间: {'__module__': '__main__', '__qualname__': 'MyClass'}
super().__init__(class_name, class_bases, class_dict)
# 创建一个继承元类的类
class MyClass(object, metaclass=MyType): # 元类的使用采用metaclass关键字声明
pass
MyClass()
【2】进阶使用
- 限制类名必须首字母大写
class MyType(type):
def __init__(cls, class_name, class_bases, class_dict):
print("当前类的本身:", cls)
# 当前类的本身: <class '__main__.MyClass'>
print("当前类的类名:", class_name)
# 当前类的类名: MyClass
print("当前类的父类:", class_bases)
# 当前类的父类: (<class 'object'>, )
print("当前类的名称空间:", class_dict)
# 当前类的名称空间: {'__module__': '__main__', '__qualname__': 'MyClass'}
# 申明首字母不大写时的报错信息
if not class_name.istitle():
raise TypeError(F"类名{class_name}的首字母必须大写!")
super().__init__(class_name, class_bases, class_dict)
# 当创建一个首字母不大写的类时,会报错
class myClass(object, metaclass=MyType): # 元类的使用采用metaclass关键字声明
pass
myClass() # TypeError: 类名myClass的首字母必须大写!
【五】元类与__call__
结合的进阶使用
【1】__call__
方法
- 如果是类() ,触发的是元类中的
__call__
- 如果是对象(), 触发的是父类中的
__call__
# 创建元类
class MyType(type):
def __init__(cls, class_name, class_bases, class_dict):
print("MyType中的init方法被触发")
super().__init__(class_name, class_bases, class_dict)
def __call__(self, *args, **kwargs):
# 打印传进来的位置参数和关键字参数
print(f"位置参数args:{args},关键字参数kwargs:{kwargs}")
# 位置参数args:('ligo',),关键字参数kwargs:{'age': 18}
# 如果不写返回值,返回的值默认是就是None
# 直接把type的拿过来用 super().__call__() , 正常返回当前类的对象
obj = super().__call__(*args, **kwargs)
print('MyType中的对象触发了call方法')
return obj
# 创建一个继承元类的类
class MyClass(object, metaclass=MyType):
def __init__(self, name, age):
print("MyClass中的init方法被触发")
self.name = name
self.age = age
def __call__(self, *args, **kwargs):
print('MyClass中的对象触发了call方法')
# stu接收到的就是MyType的 __call__ 方法返回的obj对象
stu = MyClass('ligo', age=18)
print(stu) # <__main__.MyClass object at 0x000002397F597D60>
print(stu.name) # ligo
print(stu())
# MyType中的init方法被触发
# MyClass中的init方法被触发
# MyType中的对象触发了call方法
# MyClass中的对象触发了call方法
【2】定制对象的产生过程
- 不使用关键字传参会报错
# 创建元类
class MyType(type):
def __init__(cls, class_name, class_bases, class_dict):
print("MyType中的init方法被触发")
super().__init__(class_name, class_bases, class_dict)
def __call__(self, *args, **kwargs):
# 打印传进来的位置参数和关键字参数
print(f"位置参数args:{args},关键字参数kwargs:{kwargs}")
# 位置参数args:('ligo',),关键字参数kwargs:{'age': 18}
# 定制对象的产生条件:只能通过关键字传参创建对象,不允许通过位置传参
if args:
raise TypeError(f'{self.__name__} 产生对象只能通过关键字传参创建')
# 如果不写返回值,返回的值默认是就是None
# 直接把type的拿过来用 super().__call__() , 正常返回当前类的对象
obj = super().__call__(*args, **kwargs)
print('MyType中的对象触发了call方法')
return obj
# 创建一个继承元类的类
class MyClass(object, metaclass=MyType):
def __init__(self, name, age):
print("MyClass中的init方法被触发")
self.name = name
self.age = age
def __call__(self, *args, **kwargs):
print('MyClass中的对象触发了call方法')
# stu接收到的就是MyType的 __call__ 方法返回的obj对象
stu = MyClass('ligo', 18)
print(stu) # <__main__.MyClass object at 0x000002397F597D60>
print(stu.name) # ligo
print(stu())
# TypeError: MyClass 产生对象只能通过关键字传参创建
【3】总结
- 高度定制类的产生过程
- 编写元类里面的
__init__
方法 - 前面的案例中,定制当前类的类名首字母必须大写 --> 元类中的
__init__
中
- 编写元类里面的
- 高度定制对象的产生过程
- 编写元类里面的
__call__
方法 - 类名() --->
__call__
方法 ---> 返回值 ---> 就是当前类的对象 - 在得到对象之前对对象的属性进行操作
- 上面的案例中,定制当前类初始化对象的属性的时候只能使用关键字传参数不允许位置传参数
- 编写元类里面的
【六】元类与__new__
方法结合
__new__
用于产生空对象(类),相当于人的骨架__init__
用于实例化对象(类),相当于人的血肉- 元类中的
__new__
直接调用
# 并不是所有的地方都可以直接调用__new__ 该方法过于底层
# 如果是在元类的__new__里面 可以直接调用
# 创建元类
class MyType(type):
def __init__(cls, class_name, class_bases, class_dict):
print("MyType中的init方法被触发")
super().__init__(class_name, class_bases, class_dict)
def __call__(self, *args, **kwargs):
# 得到一个空的对象
print('MyType中的对象触发了call方法')
obj = super().__call__(*args, **kwargs)
return obj
def __new__(cls, *args, **kwargs):
# 打印传进来的位置参数和关键字参数
print(f"位置参数args:{args},关键字参数kwargs:{kwargs}")
# 位置参数args:('MyClass', (<class 'object'>,), {'__module__': '__main__', '__qualname__': 'MyClass', '__init__': <function MyClass.__init__ at 0x000002B177B18CA0>, '__call__': <function MyClass.__call__ at 0x000002B177B18D30>}),关键字参数kwargs:{}
# 如果不写返回值,返回的值默认是就是None
# 直接把type的拿过来用 super().__call__() , 正常返回当前类的对象
obj = super().__new__(cls, *args, **kwargs)
print('MyType中的对象触发了new方法')
return obj
# 创建一个继承元类的类
class MyClass(object, metaclass=MyType):
def __init__(self, name, age):
print("MyClass中的init方法被触发")
self.name = name
self.age = age
def __call__(self, *args, **kwargs):
print('MyClass中的对象触发了call方法')
# stu接收到的就是MyType的 __call__ 方法返回的obj对象
stu = MyClass('ligo', age=18)
print(stu) # <__main__.MyClass object at 0x000002397F597D60>
print(stu.name) # ligo
print(stu())
# MyType中的对象触发了new方法
# MyType中的init方法被触发
# MyType中的对象触发了call方法
# MyClass中的init方法被触发
# MyClass中的对象触发了call方法
- 元类中的
__call__
间接调用
class MyType(type):
def __init__(cls, class_name, class_bases, class_dict):
print("MyType中的init方法被触发")
super().__init__(class_name, class_bases, class_dict)
def __call__(self, *args, **kwargs):
# 得到一个空的对象
print('MyType中的对象触发了call方法')
obj = super().__call__(*args, **kwargs)
return obj
def __new__(cls, *args, **kwargs):
# 打印传进来的位置参数和关键字参数
print(f"位置参数args:{args},关键字参数kwargs:{kwargs}")
# 位置参数args:('MyClass', (<class 'object'>,), {'__module__': '__main__', '__qualname__': 'MyClass', '__init__': <function MyClass.__init__ at 0x000002B177B18CA0>, '__call__': <function MyClass.__call__ at 0x000002B177B18D30>}),关键字参数kwargs:{}
# 如果不写返回值,返回的值默认是就是None
# 直接把type的拿过来用 super().__call__() , 正常返回当前类的对象
obj = super().__new__(cls, *args, **kwargs)
print('MyType中的对象触发了new方法')
return obj
# 创建一个继承元类的类
class MyClass(object, metaclass=MyType):
def __init__(self, name, age):
print("MyClass中的init方法被触发")
self.name = name
self.age = age
def __call__(self, *args, **kwargs):
print('MyClass中的对象触发了call方法')
def __new__(cls, *args, **kwargs):
print(f'MyClass中的new方法被触发')
print(args, kwargs) # ('ligo',) {'age': 18}
obj = object.__new__(cls)
obj.__init__(*args, **kwargs)
return obj
# stu接收到的就是MyType的 __call__ 方法返回的obj对象
stu = MyClass('ligo', age=18)
print(stu) # <__main__.MyClass object at 0x000002397F597D60>
print(stu.name) # ligo
print(stu())
# MyType中的对象触发了new方法
# MyType中的init方法被触发
# MyType中的对象触发了call方法
# MyClass中的new方法被触发
# MyClass中的init方法被触发
# MyClass中的init方法被触发
# MyClass中的对象触发了call方法
分类:
Python部分 / Python进阶
, Python部分
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了