Python成长之路【第十篇】:Python基础之面向对象进阶
一、反射
什么是反射?
反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为得到一种能力(自省),这一概念的提出很快引发了计算机科学领域关于应用反射性的研究,它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩
四个可以实现自省的函数
下列方法适用于类和对象
1 判断object中有没有一个name字符串对应的方法或属性
1 获取object中的属性,如果不写default值,找不到报错,设置之后,找不到则返回default值,不报错
1 设置object中name的值,有则修改,没有则创建
1 删除object中name属性,存在则删除,不存在则报错
1 class BlackMedium: 2 face = "ugly" 3 def __init__(self,name,addr): 4 self.name = name 5 self.addr = addr 6 7 def sell_houres(self): 8 print("[%s]正在卖房子" %self.name) 9 10 def rent_houres(self): 11 print("[%s]正在租房子" % self.name) 12 13 b1 = BlackMedium("万称质地","田璐元") 14 15 # 检测是否含有某属性 16 print(hasattr(b1,"name")) # 有为True 17 print(hasattr(b1,"nam1564165e")) # 没有为False 18 19 # 获取属性 20 print(getattr(b1,"name")) # 有则打印属性 21 # print(getattr(b1,"name56+")) # 没有则报错 22 print(getattr(b1,"nam456456e","没有这个属性")) # 可以设置找不到的提示,不会报错 23 24 # 设置属性 25 setattr(b1,"qwe",123) # 没有则创建 26 print(b1.__dict__) 27 setattr(b1,"addr","sss") # 有则修改 28 print(b1.__dict__) 29 30 # 删除属性 31 delattr(b1,"addr") # 有则删除 32 # delattr(b1,"nam4156456e") # 没有则报错 33 print(b1.__dict__) 34 35 # 补充:也可以增加函数属性 36 37 setattr(b1,"func",lambda x:x+1) 38 print(b1.__dict__) 39 print(b1.func(10)) 40 setattr(b1,"func1",lambda self:self.name+"444") 41 print(b1.__dict__) 42 print(b1.func1(b1))
反射补充:
1 # 观点:一切皆对象 2 3 # 需求: 4 # 利用自省(反射)检测模块中的属性,就是py文件中的属性 5 6 import sys 7 x = 1 8 y = 2 9 obj = sys.modules[__name__] # 拿到当前模块(文件)这个对象 10 print(hasattr(obj,"x"))
为什么要用反射呢?
有俩程序员,一个小明,一个小刚,额。。名字有点水,不过不用在意细节,小明在写程序的时候需要用到小刚所写的类,但是小刚和女朋友去度蜜月去了,还没有完成他写的类,小明想到了反射,使用了反射机制的小明可以继续完成自己的代码,等
小刚度蜜月回来之后再继续完成类的定义,并且去实现小明想要的功能
总之反射的好处就是,可以事先定义好接口,接口只有在被完成后才会真正的执行,这实现了即插即用,这其实是一种“后期绑定”,什么意思?意思就是,你可以先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能
1 class Ftpclient: 2 "ftp客户端,但是还没有实现具体的功能" 3 def __init__(self,addr): 4 print("正在连接服务器[%s]" %addr) 5 self.addr = addr
1 from ftp import Ftpclient 2 3 f1 = Ftpclient("192.168.1.1") 4 5 if hasattr(f1,"get"): 6 func_get = getattr(f1,"get") 7 func_get() 8 else: 9 print("--->不存在此方法") 10 print("处理其他逻辑")
1 class Ftpclient: 2 "ftp客户端,但是还没有实现具体的功能" 3 def __init__(self,addr): 4 print("正在连接服务器[%s]" %addr) 5 self.addr = addr 6 def get(self): 7 print("正在上传")
这时候我们定义完get方法之后,再来运行一下小明的程序
动态导入模块
1 # print("------>") 2 # def test1(): 3 # print("test1") 4 # def _test2(): 5 # print("test2") 6 7 # 将上方代码写入目录为 m1/t.py 8 # 然后再写一个与m1同级的.py文件,下方全部为此文件内容 9 10 # 第一种: 11 a = __import__("m1.t") # 这行代码一旦执行,就会执行t.py文件 12 print(a) 13 # 这时候我们发现只拿到了顶级的模块,也只能拿到这个最顶级的 14 # 那我们想要执行t.py文件内的test1函数怎么办呢 15 a.t.test1() 16 # 这样就执行了,需要通过两次.的方式,因为我们只拿到了顶级的模块 17 18 # 第二种: 19 from m1.t import * 20 test1() 21 # test2() # 报错 22 # _test2() # 报错 23 # 因为import * 不能导入隐藏属性,也就是变量名前有下划线的属性 24 25 # 那我们换一种方法试试 26 from m1.t import test1,_test2 27 28 test1() 29 _test2() 30 # 成功,Python并没有真正的限制 31 32 # 第三种: 33 import importlib 34 a = importlib.import_module("m1.t") 35 print(a) 36 # 这时候我们就会发现,我们定位到了t.py模块这一级别 37 # 这样我们就可以直接用a来调用方法了 38 a.test1() 39 a._test2() # 注意!下划线开头的变量名,只是在 import * 的时候不能被导入
二、__getattr__、__setattr__、__delattr__
1 # __getattr__ 2 3 class Foo: 4 x = 1 5 def __init__(self,y): 6 self.y = y 7 def __getattr__(self, item): 8 print("执行__getattr__") 9 10 f1 = Foo(10) 11 print(f1.y) 12 print(getattr(f1,"y")) 13 f1.ssssss # 找不到属性的时候将会执行内置函数__getattr__
1 # __setattr__ 2 3 class Foo: 4 x = 1 5 def __init__(self,y): 6 self.y = y 7 def __setattr__(self, key, value): 8 print("执行__setattr__") 9 # self.key = value # 报错,因为进入了无限递归 10 self.__dict__[key] = value 11 # 因为设置属性底层就是操作的字典,所以我们直接操作字典就行了,这样就解决了无限递归 12 f1 = Foo(10) 13 print(f1.__dict__) 14 f1.a = 1 15 print(f1.__dict__)
1 # __delattr__ 2 3 class Foo: 4 x = 1 5 def __init__(self,y): 6 self.y = y 7 def __delattr__(self, item): 8 print("执行__delattr__") 9 10 f1 = Foo(10) 11 del f1.x # 此行代码一旦运行,便会运行内置函数__delattr__ 12 del f1.y # 此行代码一旦运行,便会运行内置函数__delattr__
补充:这三个是给类的实例去用的
三、__getattribute__
class Foo: y = 1 def __init__(self,x): self.x = x def __getattr__(self, item): print("执行__getattr__") def __getattribute__(self, item): print("执行__getattribute__") raise AttributeError("抛出异常啦") f1 = Foo(10) f1.x # 可以找到x属性,会执行__getattribute__方法 f1.xxxxx # 不可以找到y属性,也会执行__getattribute__方法 # 当__getattribute__和__getattr__ 同时存在,无论找得到还是找不到属性, # 都会执行__getattribute__, # 但只要__getattribute__抛出AttributeError的异常,就会执行__getattr__
四、isinstance(obj,cls) 和 issubclass(sub,super)
isinstance(obj,cls) 检查obj这个实例(对象)是否是类(cls)的对象
class Foo: pass f1 = Foo() print(isinstance(f1,Foo)) # True
issubclass(sub,super) 判断sub这个类是不是super这个类的子类
class Foo: pass class Bar(Foo): pass print(issubclass(Bar,Foo)) # True
五、二次加工标准类型(包装)
包装:Python为大家提供了标准数据类型,以及丰富的内置方法。其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增或者改写方法,这就用到了我们刚学的继承和派生知识,(其他的标准类型均可以通过下面的方式进行二次加工)
1 # 需求: 2 # 做一个列表的添加方法,注意:只能添加字符串 3 4 class List(list): 5 def append(self, object): 6 if type(object) is str: 7 super().append(object) 8 else: 9 print("只能添加字符串") 10 l1 = List("hello world") 11 print(l1) 12 l1.append(1111) 13 l1.append("2222") 14 print(l1)
1 # 需求: 2 # 显示列表中间的元素 3 4 class List(list): 5 def show_middle(self): 6 mid_index = int(len(self)/2) 7 return self[mid_index] 8 9 l1 = List("albert") 10 print(l1.show_middle())
授权:授权是包装的一个特性,包装一个类型通常是对已存在的一些类型进行定制,这种做法可以新建,修改或删除原有产品的功能。其他的则保持原样。授权的过程,即是所有更新的功能都是由新类的某部分来处理的,但已存在的功能就授权给对象的默认属性
实现授权的关键点就是覆盖__getattr__方法
1 # 授权 2 # 一、自己定义一个文件处理类 3 4 class FileHandle: 5 def __init__(self,filename,mode = "r",encoding = "utf8"): 6 self.file = open(filename,mode,encoding = encoding) 7 self.mode = mode 8 self.encoding = encoding 9 10 def __getattr__(self, item): 11 return getattr(self.file,item) 12 f1 = FileHandle("a.txt","w+") 13 f1.write("111") 14 f1.seek(0) 15 print(f1.read())
1 import time 2 class FileHandle: 3 def __init__(self,filename,mode = "r",encoding = "utf8"): 4 self.file = open(filename,mode,encoding = encoding) 5 self.mode = mode 6 self.encoding = encoding 7 def write(self,line): 8 t = time.strftime("%Y-%m-%d %X") 9 self.file.write("%s %s" %(t,line)) 10 def __getattr__(self, item): 11 return getattr(self.file,item) 12 f1 = FileHandle("a.txt","w+") 13 f1.write("111\n") 14 f1.write("222\n") 15 f1.write("333\n") 16 f1.write("444\n")
六、__getitem__、__setitem__、__delitem
这三个内置方法是什么时候,怎么触发的呢?请点击查看代码
1 class Foo: 2 def __getitem__(self, item): 3 print("getitem") 4 def __setitem__(self, key, value): 5 print("setitem") 6 def __delitem__(self, key): 7 print("delitem") 8 9 f1 = Foo() 10 11 f1["name"] 12 # 一旦通过字典的形式,对f1字典的键值对进行查找,就会触发__getitem__方法 13 14 f1["name"] = "albert" 15 # 一旦通过字典的形式,对f1字典的键值对进行设置,就会触发__setitem__方法 16 17 del f1["name"] 18 # 一旦通过字典的形式,对f1字典的键值对进行删除,就会触发__delitem__方法 19 20 # 但是,你会发现,这三个方法中除了打印,就没有别的操作了 21 # 并没有真正的查找,设置,删除
那如何让我们自己写的这些方法有意义呢?请点击下方的查看代码
1 class Foo: 2 def __init__(self,name): 3 self.name = name 4 def __getitem__(self, item): 5 print("getitem") 6 return self.__dict__[item] 7 def __setitem__(self, key, value): 8 print("setitem") 9 self.__dict__[key] = value 10 def __delitem__(self, key): 11 print("delitem") 12 self.__dict__.pop(key) 13 14 f1 = Foo("albert") 15 16 print(f1["name"]) 17 # getitem 18 # albert 19 20 f1["name"] = "123" 21 print(f1.__dict__) 22 # setitem 23 # {'name': '123'} 24 25 del f1["name"] 26 print(f1.__dict__) 27 # delitem 28 # {} 29 30 # 这样咱们自己重新定义的内置方法就变得有意义了
注意:只能通过字典的方式查找,设置,删除才会触发item系列, 不能用(.),(.)是触发attr系列的
七、__str__、__repr__、__format__
改变对象的字符串显示__str__、__repr__
自定制格式化字符串__format__
1 # __str__ 2 3 # class Foo: 4 # def __init__(self,name,age): 5 # self.name = name 6 # self.age = age 7 # 8 # def __str__(self): 9 # return "名字是%s 年龄是%s" %(self.name,self.age) 10 # 11 # f1 = Foo("albert",18) 12 # print(f1) # str(f1)--> f1.__str__() 13 # 14 # x = str(f1) # 看结果 15 # print(x) 16 # 17 # y = f1.__str__() # 看结果 18 # print(y) 19 20 21 22 23 # __repr__ 24 class Foo: 25 def __init__(self,name,age): 26 self.name = name 27 self.age = age 28 29 def __repr__(self): 30 return "名字是%s 年龄是%s" %(self.name,self.age) 31 32 f1 = Foo("albert",18) 33 print(f1) # 找不到__str__就会找__repr__ 相当于__str__的替代品 34 # 如果__str__和__repr__同时存在,就会执行__str__ 35 36 37 """ 38 str函数或者print函数--->obj.__str__() 39 repr或者交互式解释器--->obj.__repr__() 40 如果__str__没有被定义,那么就会使用__repr__来代替输出 41 注意:这俩方法的返回值必须是字符串,否则抛出异常 42 """
1 # 格式库 2 format_dic = { 3 "ymd" : "{0.year}{0.mon}{0.day}" , 4 "y-m-d" : "{0.year}-{0.mon}-{0.day}" , 5 "y:m:d" : "{0.year}:{0.mon}:{0.day}" 6 } 7 8 class Date: 9 def __init__(self,year,mon,day): 10 self.year = year 11 self.mon = mon 12 self.day = day 13 def __format__(self, format_spec): 14 print("我执行了","--->",format_spec) 15 if not format_spec or format_spec not in format_dic: 16 format_spec = "ymd" 17 fm = format_dic[format_spec] 18 return fm.format(self) 19 d1 = Date("2018","05","04") 20 21 print(format(d1)) # 如果没指定格式,会使用默认,if已决定 22 23 print(format(d1,"y:m:d")) 24 25 print(format(d1,"456465465456")) # 如果格式不存在,会使用默认,if已决定
八、__slots__
1 """ 2 1.__slots__是什么: 3 是一个类变量,变量值可以是列表,元组或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性) 4 5 2.引子: 6 使用点来访问属性本质就是在访问类或者对象的__dict__属性字典,(类的字典是共享的,而每个实例的字典是独立的) 7 8 3.为何使用__slots__: 9 字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__ 10 11 当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个 12 13 字典,这跟元组或列表很类似。在__slots__中列出属性名在内部被映射到这个数组的指定小标上,使用__slots__一个不好的地方就是我们不能再给实例添加新的属性了,只能使用在__slots__中定义的那些属性名 14 15 4.注意事项: 16 __slots__的很多特性都依赖于普通的基于字典的实现,另外,定义了__slots__后的类不在支持一些普通的特性了,比如富哦继承,大多数情况下,你应该只在那些经常被使用到的用作数据结构的类上定义__slots__比如程序中需要创建某个类的几百万个实例对象。 17 18 关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性,尽管使用__slots__可以达到这样的目的,但是这个并不是他的初衷 19 20 更多的是用来作为一个内存优化工具 21 22 注意:不推荐使用__slots__ 23 """ 24 25 class Foo: 26 __slots__ = "name" 27 28 f1 = Foo() 29 f1.name = "albert" 30 # f1.age = 18 # 报错,不能再添加 31 print(f1.name) 32 print(f1.__dict__) # 报错,现在,每个实例对象已经没有字典
九、__doc__
1 class Foo: 2 "我是描述信息" 3 pass 4 5 print(Foo.__doc__)
1 class Foo: 2 "我是描述信息" 3 pass 4 5 print(Foo.__doc__) 6 # 我是描述信息 7 8 class Bar(Foo): 9 pass 10 11 print(Bar.__doc__) 12 # None
十、__module__、__class__
先在lib\aa.py的目录
1 class C: 2 def __init__(self): 3 self.name = "albert"
然后在与lib同级的目录下写一个index.py文件
1 from lib.aa import C 2 3 c1 = C() 4 5 print(c1.name) 6 # albert 7 8 print(c1.__module__) # 判断c1这个实例来自哪个模块 9 # lib.aa 10 11 print(c1.__class__) # 判断c1这个实例来自哪个类 12 # <class 'lib.aa.C'>
十一、__del__
析构方法,当对象在内存中被释放时,自动触发执行
注意:此方法一般无需定义,因为Python是一门高级语言,程序员在使用时无需关系内存的分配和释放,因为此工作都是交给Python解释器来执行的,所以,析构函数的调用是解释器在进行垃圾回收时自动触发执行的
1 class Foo: 2 def __init__(self,name): 3 self.name = name 4 5 def __del__(self): 6 print("我执行了") 7 8 f1 = Foo("albert") 9 del f1 10 print("---------->") 11 # 总结1:f1这个实例被删除的时候会执行__del__
1 class Foo: 2 def __init__(self,name): 3 self.name = name 4 5 def __del__(self): 6 print("我执行了") 7 8 f1 = Foo("albert") 9 del f1.name 10 print("---------->") 11 # 总结2:大家看打印的执行顺序 12 # 删除f1实例内的属性,不会执行__del__,而是最后执行的__del__ 13 # 那没有删除f1这个实例为什么还是执行__del 14 # 请看用法3
1 class Foo: 2 def __init__(self,name): 3 self.name = name 4 5 def __del__(self): 6 print("我执行了") 7 8 f1 = Foo("albert") 9 # del f1.name 10 print("---------->") 11 12 # 咦~怎么什么都不做还是执行__del__呢? 13 # 其实,程序执行完后就会执行__del__ 14 # 那这又是为什么,因为程序执行完之后 15 # f1这个实例自动就被Python解释器回收掉了 16 # 也就是删除了 17 # 总结3: 18 # 其实,__del__执行的原则就是: 19 # 当f1这个实例被删除后,就会执行__del__
十二、__call__
对象后边加括号,触发执行
注意:构造方法的执行是由创建对象触发的,即:对象 = 类名();而对于__call__方法的执行是由对象后加括号触发的,即:对象()或者类()()
1 class Foo: 2 def __call__(self, *args, **kwargs): 3 print("__call__执行了") 4 5 f1 = Foo() 6 f1() # 当一个实例对象加上括号()之后就会执行他的类中的__call__方法 7 8 # 我们在实例对象的时候回用一个变量名来接收一个类名加上括号() 9 # 就像上边的f1 = Foo() 10 # 那么基于Python中一切皆对象的原则 11 # Foo这个类也是一个对象,那么这个Foo加上括号(),也就是在执行Foo这个实例对象他的类中的__call__方法
十三、__next__和__iter__实现迭代器协议
1 class Foo: 2 def __init__(self,n): 3 self.n = n 4 def __iter__(self): 5 return self 6 def __next__(self): 7 if self.n == 13: 8 raise StopIteration 9 self.n += 1 10 return self.n 11 12 f1 = Foo(10) 13 # print(next(f1)) 14 # print(next(f1)) 15 # print(next(f1)) 16 # print(next(f1)) # 报错,检测得到StopIteration异常 17 18 for i in f1: # for 循环可以捕捉到StopIteration异常,所以没报错 19 print(i)
1 class Fib: 2 def __init__(self,x): 3 self._a = 0 # 定义初始值 4 self._b = 1 # 定义初始值 5 self.x = x # 定义终止值 6 def __iter__(self): 7 return self 8 def __next__(self): 9 if self._a > self.x: 10 raise StopIteration 11 self._a,self._b = self._b,self._a + self._b 12 return self._a 13 14 f1 = Fib(100) # 传入终止值 15 print(next(f1)) 16 print(next(f1)) 17 print(next(f1)) 18 print(next(f1)) 19 print(next(f1)) 20 print("---------") 21 for i in f1: 22 print(i)
十四、描述符(__get__、__set__、__delete__)
1、描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__、__set__、__delete__中的任何一个方法,这也被称为描述符协议
__get__():调用一个属性时,触发
__set__():为一个属性赋值时,触发
__delete__():采用del删除属性时,触发
1 class Foo: 2 def __get__(self, instance, owner): 3 print("__get__") 4 def __set__(self, instance, value): 5 print("__set__") 6 def __delete__(self, instance): 7 print("__delete__")
2、描述符是干什么的:描述符的作用是用来代理另外一个类的属性的(必须把描述符定义在这个类的类属性,不能定义到构造函数中)
1 class Foo: 2 def __get__(self, instance, owner): 3 print("__get__") 4 def __set__(self, instance, value): 5 print("__set__") 6 def __delete__(self, instance): 7 print("__delete__") 8 9 f1 = Foo() 10 f1.name = "albert" 11 f1.name 12 del f1.name
1 class Foo: 2 def __get__(self, instance, owner): 3 print("__get__") 4 def __set__(self, instance, value): 5 print("__set__") 6 def __delete__(self, instance): 7 print("__delete__") 8 class Bar: 9 # 何地? 10 x = Foo() # Foo()为描述符,描述x 11 def __init__(self,n): 12 self.n = n 13 # 何时? 14 b1 = Bar(10) 15 16 b1.x 17 # __get__ 18 19 b1.x = 10 20 # __set__ 21 22 del b1.x 23 # __delete__ 24 25 # 一旦对x进行操作,那么就会去Foo去找__get__、__set__、__delete__
3、描述符分两种:
一、数据描述符:至少实现了__get__和__set__
class Foo: def __get__(self, instance, owner): print("__get__") def __set__(self, instance, value): print("__set__")
二、非数据描述符:没有实现__set__
class Bar: def __get__(self, instance, owner): print("__get__")
4、注意事项:
一、描述符本身应该定义成新式类,被代理的类也应该是新式类
二、必须把描述符定义成这个类的类属性,不能定义到构造函数中
三、要严格遵循该优先级,优先级由高到低的顺序是:
1.类属性
2.数据描述符
3.实例属性
4.非数据描述符
5.找不到的属性触发__getattr__(这个有点瑕疵)
1 #描述符Str 2 class Str: 3 def __get__(self, instance, owner): 4 print('Str调用') 5 def __set__(self, instance, value): 6 print('Str设置...') 7 def __delete__(self, instance): 8 print('Str删除...') 9 10 class People: 11 name=Str() 12 def __init__(self,name,age): #name被Str类代理 13 self.name=name 14 self.age=age 15 16 17 #基于上面的演示,我们已经知道,在一个类中定义描述符它就是一个类属性,存在于类的属性字典中,而不是实例的属性字典 18 19 #那既然描述符被定义成了一个类属性,直接通过类名也一定可以调用吧,没错 20 People.name #恩,调用类属性name,本质就是在调用描述符Str,触发了__get__() 21 22 People.name='albert' #那赋值呢,我去,并没有触发__set__() 23 del People.name #赶紧试试del,我去,也没有触发__delete__() 24 #结论:描述符对类没有作用-------->傻逼到家的结论 25 26 ''' 27 原因:描述符在使用时被定义成另外一个类的类属性,因而类属性比二次加工的描述符伪装而来的类属性有更高的优先级 28 People.name #恩,调用类属性name,找不到就去找描述符伪装的类属性name,触发了__get__() 29 30 People.name='egon' #那赋值呢,直接赋值了一个类属性,它拥有更高的优先级,相当于覆盖了描述符,肯定不会触发描述符的__set__() 31 del People.name #同上 32 '''
1 #描述符Str 2 class Str: 3 def __get__(self, instance, owner): 4 print('Str调用') 5 def __set__(self, instance, value): 6 print('Str设置...') 7 def __delete__(self, instance): 8 print('Str删除...') 9 10 class People: 11 name=Str() 12 def __init__(self,name,age): #name被Str类代理 13 self.name=name 14 self.age=age 15 16 17 p1=People('albert',18) 18 19 #如果描述符是一个数据描述符(即有__get__又有__set__),那么p1.name的调用与赋值都是触发描述符的操作,于p1本身无关了,相当于覆盖了实例的属性 20 p1.name='asdsds' 21 p1.name 22 print(p1.__dict__)#实例的属性字典中没有name,因为name是一个数据描述符,优先级高于实例属性,查看/赋值/删除都是跟描述符有关,与实例无关了 23 del p1.name
5、描述符的应用
众所周知,python是弱类型语言,即参数的赋值没有类型的限制,下面我们通过描述符机制来实现类型限制功能
需求:在初始化类的时候,必须按照,name为字符串类型,age为整型类型,salary为浮点型类型
part1
我们先简单的写一个框架
1 class Typed: 2 def __get__(self, instance, owner): 3 print("__get__") 4 print("instance的参数是【%s】" %instance) 5 print("owner的参数是【%s】" %owner) 6 def __set__(self, instance, value): 7 print("__set__") 8 print("instance的参数是【%s】" %instance) 9 print("value的参数是【%s】" %value) 10 def __delete__(self, instance): 11 print("__delete__") 12 print("instance的参数是【%s】" %instance) 13 14 class People: 15 name = Typed() 16 def __init__(self,name,age,salary): 17 self.name = name 18 self.age = age 19 self.salary = salary 20 21 p1 = People("albert",18,13.1) 22 23 p1.name 24 print(p1.__dict__)
part2
然后为第一个参数(name)进行类型限制设置
1 class Typed: 2 def __init__(self,x): 3 self.x = x 4 def __get__(self, instance, owner): 5 print("__get__") 6 # print("instance的参数是【%s】" %instance) 7 # print("owner的参数是【%s】" %owner) 8 def __set__(self, instance, value): 9 print("__set__") 10 # print("instance的参数是【%s】" %instance) 11 # print("value的参数是【%s】" %value) 12 if not isinstance(value,str): # 检测value是否为str实例 13 raise TypeError("输入类型错误") 14 instance.__dict__[self.x] = value 15 def __delete__(self, instance): 16 print("__delete__") 17 # print("instance的参数是【%s】" %instance) 18 19 class People: 20 name = Typed("name") 21 def __init__(self,name,age,salary): 22 self.name = name 23 self.age = age 24 self.salary = salary 25 26 # p1 = People(123,18,13.1) # 报错,因为第一个参数不是字符串类型 27 p1 = People("albert",18,13.1) 28 print(p1.__dict__)
part3
最后,对所有的参数进行类型限制,在这,我写了两个版本
有什么区别呢?
其一是:我自己的版本,通过老师的讲解,我自己的想法是定义了两个字典来实现报错信息的其中内容
其二是:老师讲解的版本,直接将类型当参数传入,然后当做报错信息的其中内容
为什么我非要写自己的版本?
只是为了练习!!!
1 class Typed: 2 type_0 = { 3 "name":"str", 4 "age":"int", 5 "salary":"float" 6 } 7 wrong = { 8 "name": "输入的内容不是字符串", 9 "age": "输入的内容不是整型", 10 "salary": "输入的内容不是浮点型" 11 } 12 def __init__(self,x): 13 self.x = x 14 def __get__(self, instance, owner): 15 print("__get__") 16 # print("instance的参数是【%s】" %instance) 17 # print("owner的参数是【%s】" %owner) 18 def __set__(self, instance, value): 19 print("__set__") 20 # print("instance的参数是【%s】" %instance) 21 # print("value的参数是【%s】" %value) 22 if not isinstance(value,eval(self.type_0[self.x])): # 检测类型 23 raise TypeError("%s %s" %(self.x,self.wrong[self.x])) 24 instance.__dict__[self.x] = value 25 def __delete__(self, instance): 26 print("__delete__") 27 # print("instance的参数是【%s】" %instance) 28 29 class People: 30 name = Typed("name") 31 age = Typed("age") 32 salary = Typed("salary") 33 def __init__(self,name,age,salary): 34 self.name = name 35 self.age = age 36 self.salary = salary 37 38 p1 = People("albert",18,13.1) # 测试,可将本行代码复制,改其中参数的类型,查看报错信息 39 print(p1.__dict__)
1 class Typed: 2 def __init__(self,x,y): 3 self.x = x 4 self.y = y 5 def __get__(self, instance, owner): 6 print("__get__") 7 # print("instance的参数是【%s】" %instance) 8 # print("owner的参数是【%s】" %owner) 9 def __set__(self, instance, value): 10 print("__set__") 11 # print("instance的参数是【%s】" %instance) 12 # print("value的参数是【%s】" %value) 13 if not isinstance(value,self.y): # 检测类型 14 raise TypeError("%s 输入的内容不是 %s" %(self.x,self.y)) 15 instance.__dict__[self.x] = value 16 def __delete__(self, instance): 17 print("__delete__") 18 # print("instance的参数是【%s】" %instance) 19 20 class People: 21 name = Typed("name",str) 22 age = Typed("age",int) 23 salary = Typed("salary",float) 24 def __init__(self,name,age,salary): 25 self.name = name 26 self.age = age 27 self.salary = salary 28 29 p1 = People("albert",18,13.1) # 测试,可将本行代码复制,改其中参数的类型,查看报错信息 30 print(p1.__dict__)
目前我们已经实现功能了,但是问题是,如果我们的类有很多属性呢,你仍然采用在定义一堆属性的方式实现?low,这时,我们来用装饰器试试
1 def deco(obj): 2 print("类的装饰器开始运行了————>",obj) 3 return obj 4 5 @deco # Foo = deco(Foo) 6 class Foo: 7 def __init__(self,name,age,salary): 8 self.name = name 9 self.age = age 10 self.salary = salary 11 12 f1 = Foo("albert",18,13.3)
1 def deco(**kwargs): 2 def wrapper(cls): 3 print('类的装饰器开始运行啦------>',kwargs) 4 return cls 5 return wrapper 6 7 @deco(name=str,age=int,salary=float) #有参:1.运行typeassert(...)返回结果是decorate,此时参数都传给kwargs 2.People=decorate(People) 8 class People: 9 def __init__(self,name,age,salary): 10 self.name=name 11 self.age=age 12 self.salary=salary 13 14 p1=People('albert',18,13.3)
最后!
1 def deco(**kwargs): 2 def wraaper(obj): 3 for key,val in kwargs.items(): 4 setattr(obj,key,Typed(key,val)) 5 6 return obj 7 return wraaper 8 9 class Typed: 10 def __init__(self,x,y): 11 self.x = x 12 self.y = y 13 def __get__(self, instance, owner): 14 pass 15 def __set__(self, instance, value): 16 if not isinstance(value,self.y): 17 raise TypeError("%s 输入的内容不是 %s" %(self.x,self.y)) 18 instance.__dict__[self.x] = value 19 def __delete__(self, instance): 20 print("__delete__") 21 22 @deco(name = str,age = int) 23 class People: 24 25 def __init__(self,name,age,salary): 26 self.name = name 27 self.age = age 28 self.salary = salary 29 30 p1 = People("albert",18,13.1) # 更改参数类型进行测试
6、描述符总结
描述符是可以实现大部分Python类特性中的底层魔法,包括@property,@classmethod,@staticmethd 甚至是__slots__属性
描述符是很多高级库和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件
7、利用描述符原理完成一个自定制@property,实现延迟计算(本质就是把一个函数属性利用装饰器原理做成一个描述符:类的属性字典中函数名为key,value为描述符类产生的对象)
1 class Room: 2 def __init__(self,name,width,length): 3 self.name=name 4 self.width=width 5 self.length=length 6 7 @property 8 def area(self): 9 return self.width * self.length 10 11 r1=Room('albert',1,1) 12 print(r1.area)
1 class lazyproperty: 2 def __init__(self,func): 3 self.func = func 4 5 def __get__(self, instance, owner): 6 res = self.func(instance) 7 return res 8 class Room: 9 def __init__(self,name,width,length): 10 self.name = name 11 self.width = width 12 self.length = length 13 14 @lazyproperty 15 def area(self): 16 return self.width * self.length 17 18 r1 = Room("cesuo",2,3) 19 print(r1.area) 20 # print(Room.area) # 报错,AttributeError: 'NoneType' object has no attribute 'width' 21 22 23 # 如果我们用类名来调用area属性,就会报错,如果想定制成和Python一样怎么做呢 24 # 我们自己定制的lazyproperty还少一个判断,加一个判断 25 26 class lazyproperty: 27 def __init__(self,func): 28 self.func = func 29 30 def __get__(self, instance, owner): 31 if instance is None: 32 return self 33 res = self.func(instance) 34 return res 35 class Room: 36 def __init__(self,name,width,length): 37 self.name = name 38 self.width = width 39 self.length = length 40 41 @lazyproperty 42 def area(self): 43 return self.width * self.length 44 45 r1 = Room("cesuo",2,3) 46 print(Room.area)
1 class lazyproperty: 2 def __init__(self,func): 3 self.func = func 4 5 def __get__(self, instance, owner): 6 print("get") 7 if instance is None: 8 return self 9 res = self.func(instance) 10 return res 11 class Room: 12 def __init__(self,name,width,length): 13 self.name = name 14 self.width = width 15 self.length = length 16 17 @lazyproperty 18 def area(self): 19 return self.width * self.length 20 21 r1 = Room("cesuo",2,3) 22 print(r1.area) 23 print(r1.area) 24 print(r1.area) 25 print(r1.area) 26 print(r1.area) 27 # 我们会发现每次调用都会重新计算一次,我们现在不觉得有什么问题,因为计算的过程很简单嘛 28 # 但是如果我们计算的过程很复杂呢,每次都执行一次,就显得不够完善了 29 # 那我们要怎么做呢?请把下面的代码打开 30 31 32 class lazyproperty: 33 def __init__(self,func): 34 self.func = func 35 36 def __get__(self, instance, owner): 37 print("get") 38 if instance is None: 39 return self 40 res = self.func(instance) 41 setattr(instance,self.func.__name__,res) 42 return res 43 class Room: 44 def __init__(self,name,width,length): 45 self.name = name 46 self.width = width 47 self.length = length 48 49 @lazyproperty 50 def area(self): 51 return self.width * self.length 52 53 r1 = Room("cesuo",2,3) 54 55 56 # 在return res之前我们,将res的值加入实例的字典中 57 print(r1.area) 58 print(r1.area) 59 print(r1.area) 60 # 这时候我们发现,第二次后的调用,直接去实例的字典中去拿执行结果了,就不用再次计算了 61 # 为什么会直接去实例的字典,而不去描述符的get方法里呢? 62 # 因为优先级的问题,实例属性>非数据描述符
1 class lazyproperty: 2 def __init__(self,func): 3 self.func = func 4 5 def __get__(self, instance, owner): 6 print("get") 7 if instance is None: 8 return self 9 res = self.func(instance) 10 setattr(instance,self.func.__name__,res) 11 return res 12 def __set__(self, instance, value): 13 pass 14 class Room: 15 def __init__(self,name,width,length): 16 self.name = name 17 self.width = width 18 self.length = length 19 20 @lazyproperty 21 def area(self): 22 return self.width * self.length 23 24 r1 = Room("cesuo",2,3) 25 26 print(r1.area) 27 print(r1.area) 28 print(r1.area) 29 print(r1.area) 30 # 缓存功能失效,每次都去找描述符了,为何 31 # 因为描述符实现了set方法,它由非数据描述符变成了数据描述符 32 # 数据描述符比实例属性有更高的优先级,因而所有的属性操作都去找描述符了
8、利用描述符原理完成一个自定制@classmethod
1 class ClassMethod: 2 def __init__(self,func): 3 self.func=func 4 5 def __get__(self, instance, owner): #类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身, 6 def feedback(): 7 print('在这里可以加功能啊...') 8 return self.func(owner) 9 return feedback 10 11 class People: 12 name='linhaifeng' 13 @ClassMethod # say_hi=ClassMethod(say_hi) 14 def say_hi(cls): 15 print('你好啊,帅哥 %s' %cls.name) 16 17 People.say_hi() 18 19 p1=People() 20 p1.say_hi() 21 #疑问,类方法如果有参数呢,好说,好说 22 23 class ClassMethod: 24 def __init__(self,func): 25 self.func=func 26 27 def __get__(self, instance, owner): #类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身, 28 def feedback(*args,**kwargs): 29 print('在这里可以加功能啊...') 30 return self.func(owner,*args,**kwargs) 31 return feedback 32 33 class People: 34 name='Albert' 35 @ClassMethod # say_hi=ClassMethod(say_hi) 36 def say_hi(cls,msg): 37 print('你好啊,帅哥 %s %s' %(cls.name,msg)) 38 39 People.say_hi('你在干嘛') 40 41 p1=People() 42 p1.say_hi('你在干嘛')
9、利用描述符原理完成一个自定制@staticmethod
1 class StaticMethod: 2 def __init__(self,func): 3 self.func=func 4 5 def __get__(self, instance, owner): #类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身, 6 def feedback(*args,**kwargs): 7 print('在这里可以加功能啊...') 8 return self.func(*args,**kwargs) 9 return feedback 10 11 class People: 12 @StaticMethod# say_hi=StaticMethod(say_hi) 13 def say_hi(x,y,z): 14 print('------>',x,y,z) 15 16 People.say_hi(1,2,3) 17 18 p1=People() 19 p1.say_hi(4,5,6)
十五、再看property
一个静态属性property本质就是实现get,set,delete三种方法
1 class Foo: 2 @property 3 def AAA(self): 4 print('get的时候运行我啊') 5 6 @AAA.setter 7 def AAA(self,value): 8 print('set的时候运行我啊') 9 10 @AAA.deleter 11 def AAA(self): 12 print('delete的时候运行我啊') 13 14 #只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter 15 f1=Foo() 16 f1.AAA 17 f1.AAA='aaa' 18 del f1.AAA
1 class Foo: 2 def get_AAA(self): 3 print('get的时候运行我啊') 4 5 def set_AAA(self,value): 6 print('set的时候运行我啊') 7 8 def delete_AAA(self): 9 print('delete的时候运行我啊') 10 AAA=property(get_AAA,set_AAA,delete_AAA) #内置property三个参数与get,set,delete一一对应 11 12 f1=Foo() 13 f1.AAA 14 f1.AAA='aaa' 15 del f1.AAA
怎么用?
1 class Goods: 2 3 def __init__(self): 4 # 原价 5 self.original_price = 100 6 # 折扣 7 self.discount = 0.8 8 9 @property 10 def price(self): 11 # 实际价格 = 原价 * 折扣 12 new_price = self.original_price * self.discount 13 return new_price 14 15 @price.setter 16 def price(self, value): 17 self.original_price = value 18 19 @price.deleter 20 def price(self): 21 del self.original_price 22 23 24 obj = Goods() 25 obj.price # 获取商品价格 26 obj.price = 200 # 修改商品原价 27 print(obj.price) 28 del obj.price # 删除商品原价
1 #实现类型检测功能 2 3 #第一关: 4 class People: 5 def __init__(self,name): 6 self.name=name 7 8 @property 9 def name(self): 10 return self.name 11 12 # p1=People('alex') #property自动实现了set和get方法属于数据描述符,比实例属性优先级高,所以你这面写会触发property内置的set,抛出异常 13 14 15 #第二关:修订版 16 17 class People: 18 def __init__(self,name): 19 self.name=name #实例化就触发property 20 21 @property 22 def name(self): 23 # return self.name #无限递归 24 print('get------>') 25 return self.DouNiWan 26 27 @name.setter 28 def name(self,value): 29 print('set------>') 30 self.DouNiWan=value 31 32 @name.deleter 33 def name(self): 34 print('delete------>') 35 del self.DouNiWan 36 37 p1=People('alex') #self.name实际是存放到self.DouNiWan里 38 print(p1.name) 39 print(p1.name) 40 print(p1.name) 41 print(p1.__dict__) 42 43 p1.name='egon' 44 print(p1.__dict__) 45 46 del p1.name 47 print(p1.__dict__) 48 49 50 #第三关:加上类型检查 51 class People: 52 def __init__(self,name): 53 self.name=name #实例化就触发property 54 55 @property 56 def name(self): 57 # return self.name #无限递归 58 print('get------>') 59 return self.DouNiWan 60 61 @name.setter 62 def name(self,value): 63 print('set------>') 64 if not isinstance(value,str): 65 raise TypeError('必须是字符串类型') 66 self.DouNiWan=value 67 68 @name.deleter 69 def name(self): 70 print('delete------>') 71 del self.DouNiWan 72 73 p1=People('alex') #self.name实际是存放到self.DouNiWan里 74 # p1.name=1 # 报错,必须是字符串
注:十五 from to林海峰老师
十六、__enter__、__exit__
我们都知道在操作文件对象的时候可以这么写
with open("a.txt") as f: "代码块"
上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法
1 class Open: 2 def __init__(self,name): 3 self.name = name 4 def __enter__(self): 5 print("出现with语句,对象__enter__被触发,有返回值则赋值给as声明的变量") 6 return self 7 def __exit__(self, exc_type, exc_val, exc_tb): 8 print("with语句中代码块执行完毕后执行我啊") 9 10 with Open("a.txt") as f: 11 print("代码块") 12 print("代码块") 13 print("代码块") 14 print("代码块") 15 print("代码块") 16 print(f) # 这里可以看到f就是实例 17 18 # 总结: 19 # with Open("a.txt) 执行后,自动去执行了__enter__去了,有返回值则给as声明的变量 20 # __exit__是在with 语句中的代码块执行完之后才会执行
__exit__中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则异常后边的代码都无法执行
1 class Open: 2 def __init__(self,name): 3 self.name=name 4 5 def __enter__(self): 6 print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量') 7 8 def __exit__(self, exc_type, exc_val, exc_tb): 9 print('with中代码块执行完毕时执行我啊') 10 print(exc_type) # 异常类 11 print(exc_val) # 异常值 12 print(exc_tb) # 追溯信息 13 14 15 16 with Open('a.txt') as f: 17 print('=====>执行代码块') 18 raise AttributeError('***着火啦,救火啊***') 19 print('0'*100) #------------------------------->不会执行
如果__exit__的返回值为True,那么异常会被清空,就好像什么都没发生一样,直接跳出with代码块,直接执行后边的代码,因为执行了__exit__嘛
1 class Open: 2 def __init__(self,name): 3 self.name=name 4 5 def __enter__(self): 6 print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量') 7 8 def __exit__(self, exc_type, exc_val, exc_tb): 9 print('with中代码块执行完毕时执行我啊') 10 print(exc_type) 11 print(exc_val) 12 print(exc_tb) 13 return True 14 15 16 17 with Open('a.txt') as f: 18 print('=====>执行代码块') 19 raise AttributeError('***着火啦,救火啊***') 20 print("**********") # 这里不会执行,因为已经执行了__exit__ 21 print('0'*100) #------------------------------->会执行
1 class Open: 2 def __init__(self,filepath,mode='r',encoding='utf-8'): 3 self.filepath=filepath 4 self.mode=mode 5 self.encoding=encoding 6 7 def __enter__(self): 8 # print('enter') 9 self.f=open(self.filepath,mode=self.mode,encoding=self.encoding) 10 return self.f 11 12 def __exit__(self, exc_type, exc_val, exc_tb): 13 # print('exit') 14 self.f.close() 15 return True 16 def __getattr__(self, item): 17 return getattr(self.f,item) 18 19 with Open('a.txt','w') as f: 20 print(f) 21 f.write('aaaaaa') 22 f.wasdf #抛出异常,交给__exit__处理
用途或者说好处:
1、使用with语句的目的就是把代码块放在with中执行,with结束后,自动完成清理工作,无需手动干预
2、在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以再__exit__中定制自动释放内存的机制,你无需再去关心这个问题,这将大有用处
十七、元类
1、引子
class Foo: pass f1 = Foo() # f1是通过Foo类实例化的对象
python中一切皆对象,类本身也是一个对象,当使用关键字class的时候,Python解释器在加载class的时候就会创建一个对象(这里的对象指的是类而非类的实例)
上例可以看出f1是由Foo这个类产生的对象,而Foo本身也是对象,那它又是那个类产生的呢?
class Foo: pass f1 = Foo() # f1是通过Foo类实例化的对象 # type函数可以查看类型,也可以用来查看对象的类,两者是一样的 print(type(f1)) # 输出:<class '__main__.Foo'> 表示f1对象有Foo类创建 print(type(Foo)) # 输出:<class 'type'>
2、什么是元类?
元类是类的类,是类的模板
元类是用来控制如何创建类的,正如类是创建对象的模板一样
元类的实例为类,正如类的实例为对象(f1对象是Foo类的一个实例,Foo类是type类的一个实例)
3、创建类的两种方式
方式一:
class Foo: def func(self): print("from func")
方式二:
def func1(self): print("from func1") x = 2 Foo = type("Foo",(object,),{"func1":func1,"x":x}) print(Foo.x)
4、一个类没有声明自己的元类,默认它的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元类(顺便我们也可以瞅瞅元类如何控制类的创建,工作流程是什么)
1 class MyType(type): 2 def __init__(self,a,b,c): 3 print("开始执行元类的构造函数") 4 print(a) 5 print(b) 6 print(c) 7 8 class Foo(metaclass = MyType): # Foo = MyType(Foo,"Foo",(object),{}) 9 def __init__(self,name): 10 self.name = name 11 12 f1 = Foo("albert")
为什么Foo("albert")就能自动执行__init__呢,必然是有一个地方定义好了的啊
请看下面代码
1 class MyType(type): 2 def __init__(self,a,b,c): 3 print("开始执行元类的构造函数") 4 print(a) 5 print(b) 6 print(c) 7 def __call__(self, *args, **kwargs): 8 print("--->") 9 print(args) 10 print(kwargs) 11 obj = object.__new__(self) # 因为所有类继承的都是object 12 self.__init__(obj,*args,**kwargs) # Foo.__init__(f1,*args,**kwargs) 13 return obj # 这里如果不return,那么f1的值会为None 14 class Foo(metaclass = MyType): # Foo = MyType(Foo,"Foo",(object),{}) 15 def __init__(self,name): 16 self.name = name # f1.name = name 17 18 f1 = Foo("albert") 19 # Foo("albert") 执行就是在执行他的call方法,为什么以前学到的是自动执行__init__呢 20 # 因为,这是call搞的鬼,让我们看似直接执行的__init__ 21 # 本质上是先执行__call__然后执行的__init__ 22 print(f1) 23 print(f1.__dict__) 24 print(Foo.__dict__)
1 class MyType(type): 2 def __init__(self,a,b,c): 3 print("开始执行元类的构造函数") 4 def __call__(self, *args, **kwargs): 5 obj = object.__new__(self) 6 self.__init__(obj,*args,**kwargs) 7 return obj 8 class Foo(metaclass = MyType): 9 def __init__(self,name): 10 self.name = name 11 f1 = Foo("albert") 12 print(f1) 13 # print(f1.__dict__) 14 # print(Foo.__dict__)
十八、补充
异常的简单了解
-end-