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时传入的参数是什么呢?这就是是类的关键组成部分:

  1. 类名 class name = "Teacher"
  2. 基类 class_bases = (object,...)
  3. **类的名称空间 ** 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   # 返回初始化好的对象
posted @ 2021-06-02 17:57  nida  阅读(70)  评论(0)    收藏  举报