zoukankan      html  css  js  c++  java
  • 疫情环境下的网络学习笔记 python 4.15

    4.15

    今日内容

    1. 反射

    2. 内置方法 __str__ __del__

      不用调,会在满足某种条件时自动触发

    3. 元类

    反射

    python:强类型,动态解释型语言,反射是python被视为动态的关键

    什么是反射:在程序运行过程中,可以“动态”获取对象的信息

    为何用反射:得到一个未知的对象,如在协同工作时得到别人代码中的对象,想查看这个对象的属性,看能够调用其中的什么功能

    • 使用 dir(obj) 得到对象的属性列表,但是列表中存放的是字符串,不能直接通过这个列表使用这个属性
    • 通过对象的 .__dict__['name'] 可以使用到这个属性

    内置函数

    通过字符串来操作属性值

    hasattr(obj,‘name’):看obj中有没有’name’ 这个属性,返回True / False

    getattr(obj,‘name’,None):相当于obj.name,若不存在name则返回默认值None,name如果是数据则返回值,是方法则返回绑定方法或函数地址

    setattr(t,‘age’,18) :等同于t.age=18

    **delattr(t,‘age’) **:等同于del t.age

    使用四个内置函数,就可以通过字符串来使用功能,可以判断字符串对应的功能是否存在,加额外的逻辑,而不是没有这个方法就报错了

    内置方法

    什么是:定义在类内部,以__开头和结尾的方法,不是用来直接调用的,而是满足某种条件后自动执行

    为什么用:为了定制化我们的类或对象

    __str____del__ 为例介绍

    如何使用内置方法

    打印对象,就会触发对象下 __str__ 方法的运行

    class A:
    	def __init__(self,name,age):
    		self.name = name
    		self.age = age
    	def __str__(self):
    		# 返回要打印的值
    		return f'{self.name}:{self.age}'
        
    obj = A('deimos',21)
    print(obj) # 自动触发 __str__,将返回值当作本次打印的结果输出,返回值必须是字符串类型
    

    清理对象,会先执行对象下的 __del__方法。清理对象包括函数结束,回收名称空间,del 对象,引用计数为0自动垃圾回收

    class A:
    	def __init__(self,name,age):
    		self.name = name
    		self.age = age
    	def __del__(self):
            print('清理了对象')
            
    obj = A('deimos',21)
    # 运行结束,回收名称空间自动执行 __del__
    

    在清理名称空间时,释放的是程序占用的内存空间,假如涉及到文件操作,占用了系统资源,此时可以在del内置方法中设置回收系统资源

    像int类没有自带的内置函数,可以自定义一个类,继承int的属性,再在自定义类中添加自己的方法

    元类介绍

    源于一句话:一切皆对象

    什么是元类:

    实例化类得到对象,类也是对象,通过类实例化产生,这个产生类的类就称作元类

    元类经过实例化得到自定义类,自定义类实例化得到对象

    使用 type(obj) 可以看到obj的类名,使用type(class) 则可以看到定义类的类,就是元类:type

    class关键字造类的步骤

    类有三个特征:类名,类的父类(object),通过类体代码拿到类的名称空间

    1. 先拿到类名

    2. 拿到基类

    3. class机制会运行类体代码,其中产生的名字都放进类的名称空间

    4. class机制会调用默认的元类type

      type(class_name,class_bases,class_dic)

      返回一个类

    # 1、类名
    class_name="People"
    # 2、类的基类
    class_bases=(object,)
    # 3、执行类体代码拿到类的名称空间
    class_dic={}
    class_body="""
    def __init__(self,name,age):
        self.name=name
        self.age=age
    
    def say(self):
        print('%s:%s' %(self.name,self.name))
    """
    exec(class_body,{},class_dic)
    # print(class_dic)
    
    # 4、调用元类
    People=type(class_name,class_bases,class_dic)
    obj = People('deimos',22)
    

    前三步都不是能控制的,第四步使用的是内置的type,有操作的空间,可以自定义元类,来控制类的产生

    如何自定义元类控制类的产生

    class Mymeta(type):
        pass
    
    class People(metalclass = Mymeta):
    	def __init__(self,name,age):
            self.name=name
            self.age=age
    
        def say(self):
            print('%s:%s' %(self.name,self.name))
    

    自定义一个元类Mymeta,必须继承元类type。此时产生类People,第四步会使用元类Mymeta,相当于:

    People = Mymeta('People',(object,),{class_dic...})
    # type会给你在基类里加object,所以python3中都是新式类
    

    调用Mymeta发生的三件事

    1. type.call 执行__new__ 先造一个空对象 People
    2. type.call 调用 Mymeta类内的 __init__方法,完成初始化对象的操作
    3. 返回初始化好的对象

    手动抛出异常:raise 异常名(‘异常信息’)

    自定义init

    在第二步,init中,可以在元类里,产生类的时候加逻辑,强制定义类的时候以固定的格式

    class Mymeta(type):
        def __init__(self,x,y,z):
            if not x.istitle():
                raise NameError('类名的首字母必须大写')
    
    class people(metaclass=Mymeta):
    
        def __init__(self,name,age):
            self.name=name
            self.age=age
    
        def say(self):
            print('%s:%s' %(self.name,self.name))
            
    # 无需实例化,定义类People的时候就会运行类体代码,自动执行Mymeta里的init,识别到类名是小写,则抛出异常
    

    调用类的时候自动造空对象的方法

    只要是调用类,都会依次调用

    1. new
    2. init
    def __new__(cls,*args,**kwargs):
    	# 自定义的逻辑,最终得到一个对象
        return obj
    

    此时,自定义了new方法覆盖了原来的new方法,无法返回新对象了,所以应该加上父类的new方法,得到一个对象

    执行new方法得到的对象,再转交给init

    • super().new
    • type().new
    super().__new__(cls,*args,**kwargs)
    # 类一定要传,否则不知道是哪个类造的对象
    

    实际上在Mymeta底层,有一个方法帮我们先调new,再调init,再返回初始化好的对象:__call__

    call方法

    内置方法 __call__ ,在对象后面加括号调用,自动运行call的代码,返回值就是call方法的返回值

    如果想把一个对象造成可以调用的,在对象的类里面添加 __call__ 方法

    类可以加括号调用,是因为元类中有call方法,用于实例化产生对象

    总结:

    • 调用对象,运行的是对象所属类的call方法

    • 调用自定义类,运行的是自定类元类的的call方法

    定制元类:可以自订生成对象和调用的方法

    属性查找

    父类不是元类,找方法只会去父类找,不会找到元类层

    类也是对象,通过点找类的属性,找不到就去父类找,在普通类这一层。如果这一层找不到则跳去上一层,去元类层找,先找自订的Mymeta,再找type

    神游整理

    元类概念

    对象由类实例化得到,python中一切皆对象,因此类也是由一个类得到的,这个生成类的类,叫做元类。再往下,元类的生成方法不必再深究了

    对一个对象使用type方法,可以看到对象的类名,对一个类使用type方法,可以看到元类名,默认是“type”

    print(type(StanfordTeacher))print(type(Mymeta))
    print(type(People))
    # <class 'type'>
    # <class '__main__.Mymeta'>
    # 看到People的元类和Mymeta的元类
    

    由此可以知道,我们在创建类的时候,由class关键字,调用了元类,产生了一个自定义的类,类似于 People = type(...)

    创建类的流程分析

    class 关键字在帮我们创建类的时候,调用了元类 type(...) 并将一些参数传入,得到具有各种属性值和方法的类。

    研究一下定义类的过程,看看创建一个类需要哪些参数

    class People(object,):
    	country = 'China'
    	def __init__(self,name,age):
    		self.name = name
    		self.age = age
    		
    	def show_info(self):
    		print(self.name)
    		print(self.age)
    # 可以看到,一个类必然有三个组成部分:类名,父类,类体代码。类体代码在没有被执行的时候只是一些字符串
    

    因此调用type会传入同样的三个参数

    1. 类名 class_name = People
    2. 父类 class_base = (object,) 元组的形式,可以有很多个父类
    3. 类的名称空间 class_dic 一个字典,在运行类体代码之后存放产生的名字

    由此,产生类的过程可以分为四个步骤

    1. 拿到类名 class_name = People

    2. 拿到父类名 class_base = object

    3. 执行类体代码:在定义类的时候就会执行,一开始就学过

      执行类体代码产生一系列名字,放进名称空间 class_dic

    4. 上面三个参数传入元类,得到一个类People

      type('People',(object,),class_dic)
      

    所以到这里,我们可以把class帮我们做的事情展开,详细是这样的

    • exec是一个内置函数,可以帮助我们模拟class的运行机制

      exec会把第第二个位置参数上的字典当作全局作用域,第三个位置参数上的字典当作局部作用域,以此来找值

    # 1、类名
    class_name="People"
    # 2、类的基类
    class_bases=(object,)
    # 3、执行类体代码拿到类的名称空间
    class_dic={}
    class_body="""
    def __init__(self,name,age):
        self.name=name
        self.age=age
    
    def say(self):
        print('%s:%s' %(self.name,self.name))
    """
    exec(class_body,{},class_dic)
    # print(class_dic)
    
    # 4、调用元类
    People=type(class_name,class_bases,class_dic)
    obj = People('deimos',22)
    

    自定义元类

    调用元类产生类的前三个步骤都没什么可操作性。值得研究的是第四个步骤。如果不指定元类,会使用python默认的元类 type 简单省事,但是对于复杂的需求,元类也可以被自定义

    指定元类的方法

    1. 创建一个自定义元类,自定义元类必须继承 type,因为需要用到type里面基层的方法

      class Mymeta(type):
      	pass
      
    2. 创建自定义类,在括号里指定使用的元类 metaclass = Mymeta

      class People(object,metaclass = Mymeta):
      	def __init__(self,name,age):
      	self.name=name
      	self.age=age
      
      def say(self):
      	print('%s:%s' %(self.name,self.name))
      

    这样,我们就完成了类的元类的指定。再生成新类,就相当于

    People=Mymeta(class_name,class_bases,class_dic)
    

    类实例化产生对象需要类中的 init 方法,同样地,元类产生类也需要 init 方法,在Mymeta元类的 init 里面加上逻辑和数据,就可以完成比起使用元类来创建类额外多的功能

    # 在init里加入逻辑判断,如果类名为小写开头,则抛出异常
    class Mymeta(type):
        def __init__(self,class_name,class_bases,class_dic):
            # 使用super来重用父类type的init
            super().__init__(class_name, class_bases, class_dic) 
    
            if class_name.islower():
                # 判断类名是否为纯小写
                raise TypeError('类名%s请修改为驼峰体' %class_name)
    
            if '__doc__' not in class_dic or len(class_dic['__doc__'].strip(' 
    ')) == 0:
                # 判断是否有文档注释
                raise TypeError('类中必须有文档注释,并且文档注释不能为空')
                
    class People(object,metaclass=Mymeta):
        """
        文档注释
        """
        school='Stanford'
    
        def __init__(self,name,age):
            self.name=name
            self.age=age
    
        def say(self):
            print(self.name,self.age)
    
    raise 自定义的错误名('提示错误信息')
    # 使用raise可以自定义抛出异常
    # 由于类体代码会在定义的时候就被执行,所以不需要实例化,只要包含了类的代码运行了,就会检查People类是不是驼峰体,有没有文档注释
    

    调用的原理

    为什么可以被调用

    __call__ 是一个内置方法,会在对象后面被加括号调用的时候自动执行。能被执行的前提是对象中有 call 方法

    class Foo:
        def __call__(self, *args, **kwargs):
            print(self)
            print(args)
            print(kwargs)
    
    obj=Foo()
    obj(11,22,33)
    
    # <__main__.Foo object at 0x000001F9A3B91280>
    # (11, 22, 33)
    # {}
    

    类,函数可以被调用,是因为他们有call方法。默认,类的call方法就是生成一个对象,在类里面可以改写这个call,实现调用类的时候的别的功能

    call 的步骤

    对应我们之前学的对象的产生过程,类的call方法有下面3步

    1. 产生一个空对象obj
    2. 调用类的__init__方法初始化obj
    3. 返回初始化好的obj
    

    同样在元类里也有 __call__ 方法,一样有以上三步,不过返回的是类,而不是对象。在自定义的元类Mymeta中可以改写call方法实现不同的需求

    与调用类相似,调用元类生成类的三个步骤,并且用到了元类中的其他内置方法:new,init

    1. 调用__new__产生一个空对象obj
    2. 调用__init__初始化空对象obj
    3. 返回初始化好的obj:return obj
    

    我们把__call__ 展开,写到自定义的元类里,长这个样:

    class Mymeta(type):
        def __call__(self, *args, **kwargs):
            # 1. 调用new,得到一个空对象obj
            obj=self.__new__(self)
            # 2. 对得到的obj执行init,初始化
            self.__init__(obj,*args,**kwargs)
            # 3. 返回初始化好的对象
            # 这也是在普通类中内置方法call的流程,只不过在类中返回的是对象,在元类中返回的是类
            return obj
        def __init__(self):
            pass
        def __new__(self):
            pass
    

    上面这个代码块什么都没做,只是按照流程来走了一遍,所以可以往里面加额外的逻辑,在生成类的时候实现额外的功能

    例 操作产生的类对象的dict,把所有属性变成私有

    class Mymeta(type):
        def __call__(self, *args, **kwargs):
            # 这里self是People类,包含People类的类名,本身自带的属性等等
            obj = self.__new__(self)
            self.__init__(obj, *args, **kwargs)
            # 对初始化得到的对象字典进行操纵
            obj.__dict__ = {'_%s__%s' % (self.__name__, k): v for k, v in obj.__dict__.items()}
            return obj
    
    class People(object,metaclass=Mymeta):
        school='Stanford'
        def __init__(self,name,age):
            self.name=name
            self.age=age
    
        def say(self):
            print(self.name,self.age)
    t1 = People('lili', 18)
    print(t1.__dict__)
    # <class '__main__.People'>
    # {'_People__name': 'lili', '_People__age': 18}
    # 属性全部变成隐藏的了
    

    属性查找问题

    对象与类之间的属性查找是先从自己开始找,接着找自己的类,再找父类,最后找object

    父类不是元类,从对象出发找属性不会找到元类去

    如果直接 类名.属性名 也可以直接访问类里面的数据或方法,这个时候可以跳出类层:当前类找不到,去父类找,都找不到,去到元类层找Mymeta,最后找不到,去元类type里找

  • 相关阅读:
    Redis常用数据类型及应用场景之Set
    Redis常用数据类型及应用场景之List
    Redis常用数据类型及应用场景之Hash
    exists & not exists
    oracle 中 dblink 的简单使用
    DockerCompose之数据卷Volume
    DockerCompose之常见编排脚本
    160308-学习State Pattern Actor
    12.3-框架维护
    12.2-机器人协作系统
  • 原文地址:https://www.cnblogs.com/telecasterfanclub/p/12706901.html
Copyright © 2011-2022 走看看