疫情环境下的网络学习笔记 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(' \n')) == 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里找

posted @ 2020-04-15 17:50  黑猫警长紧张  阅读(135)  评论(0编辑  收藏  举报