python 面向对象之元类
python 面向对象之元类
type方法的应用
1.查看数据类型
s1 = 'hello world' # str()
l1 = [11, 22, 33, 44] # list()
d1 = {'name': 'jason', 'pwd': 123} # dict()
t1 = (11, 22, 33, 44) # tuple()
print(type(s1)) # <class 'str'>
print(type(l1)) # <class 'list'>
print(type(d1)) # <class 'dict'>
print(type(t1)) # <class 'tuple'>
2.查看对象是哪个类产生的
class Student:
pass
obj = Student()
print(type(obj)) # <class '__main__.Student'>
3.类其实也是一种对象,类是由谁产生的呢
class Student:
pass
obj = Student()
print(type(obj)) # <class '__main__.Student'>
print(type(Student)) # <class 'type'>
class A:pass
class B:pass
print(type(A), type(B)) # <class 'type'> <class 'type'>
我们发现我们的类都是由type产生的。
而type就是一切类的生产者(包括它自己)被称为元类。
print(type(type)) # <class 'type'>
创建类的底层机制
我们平常都是用class关键字来创建类的:
class 类名(父类):
类体代码
而我们打开产生类的type它的源码,可以看见type除了传入对象来判断产生它的类,还有另一种用法
即传入类名,父类,类体名称空间产生一个新类。
class_body_code = """
name = 'leethon'
"""
class_dict = {}
# exec会执行代码并将一些名称传入class_dict名称空间
exec(class_body_code, {}, class_dict)
cls = type('Student', (object,), class_dict) # 传入类名,传入父类,传入名称空间
obj = cls()
print(obj.__class__) # <class '__main__.Student'>
print(obj.name) # leethon
这个过程很麻烦,而且和class关键字所实现的还有差距,所以这里只做了解。
即我们的类实际上都是由元类产生的,底层就是用了一个type方法。
通过元类控制类的产生
因为类是由元类产生的,所以可以理解为类实际上就是元类进行实例化产生的对象,
联系对比:
- 对象--通过类名()的方式产生--触发了类体中
__init__
得到了独有的属性 - 类---通过元类产生--触发了元类中的
__init__
得到了每个类所独有的类属性
所以我们可以通过派生元类中的__init__
方法达到控制类产生的目的。
那么派生自然要用到继承和super关键字,将元类type作为父类得到的类也是元类,因为它有产生类的方法。
class MyMetaClass(type):
def __init__(cls, what, bases=None, dict=None):
if not what.istitle(): # 如果类名不大写开头
raise TypeError(f'类定义必须要大写,你看看你写的{what}') # 报错终止程序
# 如果符合开头大写的命名风格就继续执行
super().__init__(what, bases, dict) # 继承父类中的方法
class A(metaclass=MyMetaClass): # 修改默认元类变为我们的元类,也修改了产生本类的方式
pass
class bbb(metaclass=MyMetaClass): # 在这里报错,TypeError: 类定义必须要大写,你看看你写的bbb
pass
如果我们选择修改产生类的元类(通过修改metaclass关键字参数的方式),那么这个元类可以是我们基于type派生的,我们通过派生type的双下init方法就可以控制类的产生过程了。
通过__new__
方法控制产生类的过程
- 实际上,类产生时触发的语法是:type(class_name,bases,dict)
- 类似于,对象产生时,也是调用类时,触发了元类中的
__call__()
一样。
类的产生经历了以下三步:
- 通过元类的双下new方法产生一个新的对象(类也是对象)
- 调用元类的双下init方法为对象添加独有属性
- 返回创建好的类
那么type函数经历了类似以下的代码
# 以下代码只是简单描述了下type,与源代码有出入,只列举了一些特征。
def type(class_name,bases,dict):
# 1.调用type的new产生一个新类
cls = type.__new__(mcs,class_name,bases,dict) # 传入元类,名、父类、名称空间
# 2.调用双下给新类一些特有的属性
type.__init__(cls, class_name, bases=None, dict=None) # 传入新类、名、父类,名称空间
# 3.返回出类
return cls
所以我们除了通过双下init控制,还可以通过派生双下new来控制类的产生过程。(无论是什么方法都要指定元类为我们自己编写的继承派生于type的元类)
而且,通过派生双下new来修改产生类的过程,影响更加深远,因为new方法是会传入mcs元类的,这个元类属性会去父类里面找,所以new方法可以不仅可以控制产生的类,还可以控制产生的类的子类的过程。
而与之相比,双下init只能修改通过这个元类产生的类的过程,因为init方法只会传入新类和一些类属性,并不拥有元类属性。
形象来记忆的话,双下new是基因改造,双下init是后天整容。
控制某个类的子类的所有字符串属性全部归拢到words字典中
class MyMetaClass(type):
def __new__(mcs, class_name, class_bases, class_attrs):
if class_name == 'Models':
return type.__new__(mcs, class_name, class_bases, class_attrs)
words = {}
for k, v in class_attrs.items():
# 所有的表的字段属性都拿出来
if isinstance(v, str): # 如果某属性的值是字符串类型就归拢到words中
words[k] = v
for k in words:
class_attrs.pop(k) # 属性中就字符串类型移除
class_attrs['words'] = words
return type.__new__(mcs, class_name, class_bases, class_attrs)
class Models(metaclass=MyMetaClass): # 类
pass
class Leethon(Models): # 子类
name = 'leethon'
age = 18
hobby = 'eat'
print(Leethon.age) # 18
print(Leethon.words)
# {'__module__': '__main__', '__qualname__': 'Leethon', 'name': 'leethon', 'hobby': 'eat'}
print(Leethon.name) # 报错,因为name已经不是这个类的属性了
通过元类控制类产生对象
看标题有些相似,但要注意区分,上一小节指控制类的产生过程,这一节要说明对象的产生过程。
-
我们在上一篇博客中提到,类的魔法方法
__call__
是在对象被加括号调用时自动触发的。 -
那么将类看成对象,产生它的元类中的
__call__
也就会在类名加括号调用时自动触发。
也就是我们平常习以为常的obj = 类名()
的方式产生对象的过程会自动触发元类中的__call__
。
理解了上面这一点后,我们就可以尝试从元类的__call__
做手脚,来控制对象的产生了。
我们可以直接通过派生的方式修改,也可以直接重写的一个call
一般来说类的元类都是type,它的__call__
,主要做了以下几件事:
class MyMetaClass(type):
def __call__(cls, *args, **kwargs):
# 1.就是产生一个空对象
obj = cls.__new__(cls)
# 2.调用类的init传入对象和参数
cls.__init__(obj, *args, **kwargs)
# 3.返回创建好的对象
return obj
# 以上的__call__只是对type中的简单模仿,建议还是直接用super派生
class A(metaclass=MyMetaClass): # 修改默认元类变为我们的元类,也修改了产生本类的方式
pass
obj = A() # 触发了MyMetaClass的__call__
print(obj) # <__main__.A object at 0x0000025DBB436610>
让产生对象时只能传入关键字参数
# 实现方式1
class MyMetaClass(type):
def __call__(cls, *args, **kwargs):
if args:
raise TypeError('你只能传入关键字参数') # 在这一步控制了只能传入关键字参数
return super().__call__(*args, **kwargs) # 自动传入调用的对象,方法是父类的方法
# 并将值返回出去,与原本call的结构一致
class A(metaclass=MyMetaClass): # 修改默认元类变为我们的元类,也修改了产生本类的方式
def __init__(self, name):
self.name = name
obj = A('leethon')
print(obj.name)
# 实现方式2(不建议)
class MyMetaClass(type):
def __call__(cls, *args, **kwargs):
# 1.就是产生一个空对象
obj = cls.__new__(cls)
# 2.调用类的init传入对象和参数
if args:
raise TypeError('你只能传入关键字参数') # 在这一步控制了只能传入关键字参数
cls.__init__(obj, *args, **kwargs)
# 3.返回创建好的对象
return obj
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!