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除了传入对象来判断产生它的类,还有另一种用法
![image-20221108155811062](C:\Users\ASUS\Pictures\md文件图片\python 面向对象之元类\image-20221108155811062.png)
即传入类名,父类,类体名称空间产生一个新类。
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方法就可以控制类的产生过程了。
通过元类控制类产生对象
看标题有些相似,但要注意区分,上一小节指控制类的产生过程,这一节要说明对象的产生过程。
-
我们在上一篇博客中提到,类的魔法方法
__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
标签:__,obj,python,元类,面向对象,print,type,class,之元类
From: https://www.cnblogs.com/Leethon-lizhilog/p/16870479.html