python学习笔记DAY21(面向对象编程-属性查找)
这是我个人的学习笔记,都赖于(egon老师)的分享,以下是老师博客的原地址:
https://www.cnblogs.com/xiaoyuanqujing/articles/11640888.html
面向对象编程_元类
一、元类的介绍
元类是什么?还是要回到我们经常提起的概念:python中一切皆对象,类本身也是,所以创建类这个对象肯定也需要一个类,元类。
class Teacher:
school = "B站大学"
def __init__(self,name,age):
self.name = name
self.age = age
def say(self):
print(f"欢迎{self.name}老师入驻{self.school}")
- 所有对象都是实例化,或者说是调用类得到的也可以,比如t01就调用了Teacher这个类。
t01 = Teacher("温铁军",70)
print(type(t01)) # 得到t01的类是Teacher :<class '__main__.Teacher'>
- 我们开始说过了,类的本质也是对象。但是类需要调用让自己成为对象的类,有一个特殊名字:元类
于是我们可以推到出:产生Teacher这个类的过程==> Teacher = 元类(...)
print(type(Teacher)) # 得到Teacher 的类是 type :<class 'type'>

二、class关键字创建类的流程分析
由上文我们知道,class关键字在帮我们创建类时,也帮我们调用了元类:type。那调用type时传入的参数是什么呢?这就是是类的关键组成部分:
- 类名 class name = "Teacher"
- 基类 class_bases = (object,...)
- **类的名称空间 ** class_dic : 类的名称空间是执行代码而得到的
综上所述:class关键字帮我们创建一个类,可以细分为以下是个过程
| 1. 拿到类名:class_name = "Teacher" |
| 2. 拿到基类们:class_bases = (object,...) |
| 3. 执行类体代码,拿到名称空间:class_dic = {...} |
| 4. 调用元类得到类:Teacher = type(class_name,class_bases,class_dic) |
三、自定义元类控制类Teacher的创建
一个类没有声明自己的元类,默认元类就是type,除了使用内置元类type,我们也可以通过继承type来自定义元类。然后通过metaclass关键字参数为一个类指定元类:
# 定义元类时,需要指明继承 type ,否则得到的只能是普通类
class Mymeta(type):
pass
# 通过元类创建类,需要给元类三个参数:
# Teacher = Mymeta("Teacher",(object),{...})
class Teacher(object,metaclass=Mymeta):
school = "B站大学"
def __init__(self,name,age):
self.name = name
self.age = age
def say(self):
print(f"欢迎{self.name}老师入驻{self.school}")
以上代码:我们为Teacher指定了一个我们自己创建的元类Mymeta,接下来就需要我们定制这个元类的具体功能了。
- 自定义元类可以控制类的产生过程,类的产生过程其实就是类调用元类的整个过程。
- 就是:Teacher = Mymeta("Teacher",(object),{...})
- 调用时产生了Teacher这个空对象,然后连同参数一起,传给Mymeta下的_init__方法,完成初始化:
class Mymeta(type):
def __init__(self,class_name,class_bases,class_dic):
super(Mymeta,self).__init__(class_name,class_bases,class_dic) # 重用父类type 的功能
if class_name.islower():
raise TypeError(f"类名{class_name}必须修改为驼峰体")
if "__doc__" not in class_dic or len(class_dic["__doc__"].strip("\n")) == 0:
raise TypeError("类中必须有文档注释,并且文档注释不能为空")
# Teacher = Mymeta("Teacher",(object),{...})
class Teacher(object,metaclass=Mymeta):
"""
类Teacher的文档注释
"""
school = "B站大学"
def __init__(self,name,age):
self.name = name
self.age = age
def say(self):
print(f"欢迎{self.name}老师入驻{self.school}")
四、自定义元类控制类Teacher的调用
储备知识:call_
- 默认每个类的内部都有一个_call__方法,才能被对象调用
class Foo:
def __call__(self, *args, **kwargs):
print(self)
print(args)
print(kwargs)
obj = Foo()
# 1.想要obj这个对象变成一个可调用的对象,需要在该对象的类中定义一个__call__方法,该方法会在调用对象时,自动触发
# 2.调用objde 返回值就是__call__方法的返回值
res = obj(1,2,3,x=1,y=2)
- 由以上代码可知:调用一个对象,就是触发对象所在类中_call__方法的执行。把Teacher也当做一个对象,那么Teacher这个对象的类中必然存在一个 call 方法:
class Mymeta(type):
def __call__(self, *args, **kwargs):
print(self)
print(args)
print(kwargs)
return 123
class Teacher(object,metaclass=Mymeta):
"""
类Teacher的文档注释
"""
school = "B站大学"
def __init__(self,name,age):
self.name = name
self.age = age
def say(self):
print(f"欢迎{self.name}老师入驻{self.school}")
# 调用Teacher 就是在调用Teacher类中的__call__方法
obj = Teacher("温铁军",70) # 然后将Teacher传给self,溢出的位置参数传给* ,溢出的关键字参数传给**
# <class '__main__.Teacher'>
# ('温铁军', 70)
# {}
# 调用Teacher的返回值就是调用__call__的返回值
print(obj) # 123
- 默认,调用 obj = Teacher("温铁军",70) 会做三件事
- 产生一个空对象obj
- 调用_init__ 方法初始化对象obj
- 返回初始化好的obj
- 对应,Teacher类中的_call__方法也是做这三件事:
class Mymeta(type):
def __call__(self, *args, **kwargs):
# 1.调用__new__产生一个空对象obj
obj = self.__new__(self) # 这里的self是 Teacher,必须穿参。
# 2.调用__init__初始化对象obj
self.__init__(obj,*args,**kwargs)
# 3.返回初始化好的对象obj
return obj
class Teacher(object,metaclass=Mymeta):
"""
类Teacher的文档注释
"""
school = "B站大学"
def __init__(self,name,age):
self.name = name
self.age = age
def say(self):
print(f"欢迎{self.name}老师入驻{self.school}")
obj = Teacher("温铁军",70)
- 以上代码,相当于一个模板,在此基础上改写_class__的逻辑,就可以控制Teacher调用元类的过程,比如将Teacher的对象的所有属性变成私有的。
class Mymeta(type):
def __call__(self, *args, **kwargs):
obj = self.__new__(self)
self.__init__(obj,*args,**kwargs)
# 自定义,为对象,造一个__dict__的方法
obj.__dict__ = {'_%s__%s' % (self.__name__,k) :v for k,v in obj.__dict__.items()}
# 返回初始化好的对象
return obj
class Teacher(object,metaclass=Mymeta):
"""
类Teacher的文档注释
"""
school = "B站大学"
def __init__(self,name,age):
self.name = name
self.age = age
def say(self):
print(f"欢迎{self.name}老师入驻{self.school}")
obj = Teacher("温铁军",70)
print(obj.__dict__) # {'_Teacher__name': '温铁军', '_Teacher__age': 70}
五、再看属性查找
class Mymeta(type):
n = 444
def __call__(self, *args, **kwargs):
obj = self.__new__(self)
self.__init__(obj,*args,**kwargs)
return obj
class Bar:
# n = 333
pass
class Foo(Bar):
# n = 222
pass
class Teacher(Foo,metaclass=Mymeta):
# n = 111
school = "B站大学"
def __init__(self,name,age):
self.name = name
self.age = age
def say(self):
print(f"欢迎{self.name}老师入驻{self.school}")
print(Teacher.n)
# 自下而上依次注释各个类中的 n ,然后启动程序,发现 n 的 查找顺序为:
# Teacher(类自己)--->Foo(父类)--->Bar(父类的父类)--->object(上帝类)--->Mymeta(元类)--->type(终极元类)
由上述代码可知:
-
属性查找分为两层
- 对象层(基于MRO列表的查找)Teacher(类自己)--->Foo(父类)--->Bar(父类的父类)--->object(上帝类)
- 类层(元类层)Mymeta(元类)--->type(终极元类)
-
分析:元类Mymeta中call_中的self.new_ 的 查找
class Mymeta(type):
n = 444
def __call__(self, *args, **kwargs):
obj = self.__new__(self)
print(self.__new__ is object.__new__) # ture
class Bar:
n = 333
# pass
class Foo(Bar):
n = 222
# pass
class Teacher(Foo,metaclass=Mymeta):
n = 111
school = "B站大学"
def __init__(self,name,age):
self.name = name
self.age = age
def say(self):
print(f"欢迎{self.name}老师入驻{self.school}")
# 触发Teacher的类中的__call__方法的执行,进而执行self.__new__开始查找
Teacher("温铁军",70)
综上所述:Mymeta下的call_里的self.new_在Teacher、Foo、Bar 中都没有找到new_的情况下,会去object里找,所以之前的类就算都没有实现new_方法,一定会在object中找到。所以就没有必要去type中查找了。
最终:
class Mymeta(type):
n = 444
def __new__(cls, *args, **kwargs):
obj = type.__new__(cls,*args,**kwargs) # 必须是这种传值方式
print(obj.__dict__)
# 只有在返回值是type的对象时,才会触发下面的__init__
return 123
def __init__(self,class_name,class_bases,class_dic):
print("run...")
class Bar:
n = 333
# pass
class Foo(Bar):
n = 222
# pass
class Teacher(Foo,metaclass=Mymeta):
n = 111
school = "B站大学"
def __init__(self,name,age):
self.name = name
self.age = age
def say(self):
print(f"欢迎{self.name}老师入驻{self.school}")
# 触发Teacher的类中的__call__方法的执行,进而执行self.__new__开始查找
print(type(Mymeta)) # <class 'type'>
# 产生类 Teacher的过程就是在调用Mymeta,而Mymeta之所以可以调用,一定是元类type中有一个__call__方法
# type中的__call__方法同样需要做三件事:
class type:
def __call__(self, *args, **kwargs): # self = <class '__main__.Mymeta'>
obj = self.__new__(self, *args, **kwargs) # 产生一个obj的空对象
self.__init__(obj, *args, **kwargs) # 初始化这个创建的对象
return obj # 返回初始化好的对象

浙公网安备 33010602011771号