面向对象进阶
1isinstance(obj,cls)和issubclass(sub,super)
isinstance(p1,A)#判断p1是否是类A的对象,返回布尔值
issubclass(B,A)#判断类B是否继承类A,即B是否是A的派生类返回布尔值
1 class A: 2 pass 3 p1 = A() 4 print(isinstance(p1,A))#判断p1是否是类A的对象,返回布尔值 5 class B(A): 6 pass 7 print(issubclass(B,A))#判断类B是否继承类A,即B是否是A的派生类返回布尔值
2.反射
主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。
python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)
四个可以实现自省的函数:
hasattr(p1,"name"))#检测对象是否含有某属性,返回布尔值
getattr(p1,"name")#获取对象的某个属性,不存在会报错,可定义第三个参数,若不存在返回定义值
setattr(p1,"salary",1000)#设置属性,若已存在会修改
delattr(p1,"salary")#删除对象的某个属性,不存在会报错
1 class Zixing: 2 def __init__(self,name,age): 3 self.name = name 4 self.age = age 5 def fangfa(self): 6 print("【%s】的年龄是%s"%(self.name,self.age)) 7 p1 = Zixing("alex",20) 8 print(hasattr(p1,"name"))#检测对象是否含有某属性,返回布尔值 9 print(hasattr(p1,"fangfa")) 10 11 n = getattr(p1,"name")#获取对象的某个属性,不存在会报错,可定义第三个参数,若不存在返回定义值 12 print(n) 13 14 setattr(p1,"salary",1000)#设置属性,若已存在会修改 15 print(p1.__dict__) 16 17 delattr(p1,"salary")#删除对象的某个属性,不存在会报错 18 delattr(p1,"aaa") 19 print(p1.__dict__)
好处一:通过反射,可以实现即插即用,定义接口的功能(可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能)
好处二:动态导入模块(基于反射当前模块成员)
1 import importlib 2 m = importlib.import_module("text.text1")#调用importlib模块导入的字符串路径文件结果将导入到指定文件路径 3 module_t = __import__("text.text1")#__import__方法导入字符串路径文件将只导入到最顶层文件处 4 print(module_t)#<module 'text' (namespace)> 5 print(m)#<module 'text.text1' from 'D:\\pycharm\\S3\\text\\text1.py
3.__setattr__,__delattr__,__getattr__
__getattr__:当用点在调用属性的时候,如果属性不存在会触发自定义的__getattr__方法,__getattribute__方法优先于__getattr__,只要一调用就会触发,只不过在找不到的情况下会交给__getattr__处理(不常用)
class C: def __init__(self,x): self.x = x def __getattr__(self, item):#若没找到调用的属性会触发 print("执行getattr!") def __getattribute__(self, item):#当__getattribute__与__getattr__同时存在,只会执行__getattrbute__,除非__getattribute__在执行过程中抛出异常AttributeError print("执行getattribute!") raise AttributeError("抛出异常了!") f = C(1) print(f.x) f.y
__setattr__:在设置值得时候会触发
class D: def __init__(self,name): self.name = name def __setattr__(self, key, value): print("__setattr__工作了!") self.key = value # 这就无限递归了,因为这是赋值操作又会马上触发__setattr__方法,错误用法 self.__dict__[key]=value#正确用法,因为设置值底层就是在操作实例属性字典 p1 = D("alex")
__delattr__:在进行删除属性操作的时候会触发
class D: def __init__(self,name): self.name = name def __delattr__(self, item): print("__delattr__工作了!") #del self.item # 这就无限递归了,因为这是删除操作又会马上触发__delattr__方法,错误用法 self.__dict__.pop(item)#正确用法,因为设置值底层就是在操作实例属性字典 p1 = D("alex") print(p1.__dict__) del p1.name print(p1.__dict__)
4.包装和授权
包装:python为大家提供了标准数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增/改写方法,这就用到了我们刚学的继承/派生知识(其他的标准类型均可以通过下面的方式进行二次加工)
包装 class List(list): def append(self, p_object): if type(p_object) is str: super().append(p_object) else: print("数据不合法!") def mid_shuzi(self): index = int(len(self)/2) print(self[index]) li = List("aaa") li.append(123) print(li) li = List("hello") li.mid_shuzi()
授权:授权是包装的一个特性, 包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建,修改或删除原有产品的功能。其它的则保持原样。授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。
实现授权的关键点就是覆盖__getattr__方法
import time class FileHandle: def __init__(self,filename,mode='r',encoding='utf-8'): self.file=open(filename,mode,encoding=encoding) def write(self,line): t=time.strftime('%Y-%m-%d %T') self.file.write('%s %s' %(t,line)) def __getattr__(self, item): return getattr(self.file,item) f1=FileHandle('b.txt','w+') f1.write('你好啊') f1.seek(0) print(f1.read()) f1.close()
5. __setitem__,__getitem,__delitem__
class Item: def __getitem__(self, item): print("执行getitem!") return self.__dict__[item] def __setitem__(self, key, value): print("执行setitem!") self.__dict__[key]=value def __delitem__(self, key): print("执行delitem!") self.__dict__.pop(key) p1 = Item() #p1.name = "alex" #print(p1.name) p1["name"]="alex"#用字典格式处理实例,会触发...item内置方法,以.方式调用触发attr方法 print(p1.__dict__) print(p1["name"])
6. __str__,__repr__,__format__
class Str: def __init__(self,name,age): self.name = name self.age = age def __str__(self):#必须有返回值,且为字符串 print("__str__运行结果如下:") return "名字是%s,年龄是%s"%(self.name,self.age) def __repr__(self):#必须有返回值,且为字符串 print("__repr__运行结果如下:") return "名字是%s,年龄是%s" % (self.name, self.age) p1 = Str("alex",18) print(p1)#print运行本质是str(p1)>>>__str(p1)___>>>__repr(p1),两者并存时,运行__str(p1),二者返回值必须是字符串,在解释器中直接先调用__repr__方法
__format__:在用formot定义输出格式时,会触发__format__方法
dic = { "ymd":"{0.year}{0.month}{0.day}", "y-m-d":"{0.year}-{0.month}-{0.day}", "y:m:d":"{0.year}:{0.month}:{0.day}" } class Riqi: def __init__(self,year,month,day): self.year = year self.month = month self.day = day def __format__(self, format_spec): f1 = dic[format_spec] return f1.format(self) p1 = Riqi(2016,12,26) print(format(p1,"ymd"))#自定义打印值的格式,通过format触发内部的__format__方法 print(format(p1,"y-m-d")) print(format(p1,"y:m:d"))
7.__slots__
class Foo: #__slots__ = "x" __slots__ = ["name","age"] p1 = Foo() p1.x = "alex"#根据类里__slots__定义的key来传值,一一对应 p1.name = "eric"#类里__slots__没有定义name这个key,无法添加 print(p1.x) print(p1.__dict__)#报错,当类里定义了__slots__方法后,实例对象不再有__dict__方法不再有属性字典, class Foo: __slots__ = ["name","age"]#设置实例传入的值对应的key p1 = Foo() p1.name = "eric" #p1.age = 18 print(p1.name) #print(p1.age)
8.__doc__
1 class A: 2 "我是描述信息" 3 pass 4 p1 = A() 5 print(p1.__doc__)#打印类描叙信息 6 class B(A): 7 pass 8 v1 = B() 9 print(v1.__doc__)#类的描述信息无法被继承
9.__next__和__iter__实现迭代器协议
1 class A: 2 def __init__(self,n): 3 self.n = n 4 def __iter__(self):#遵循迭代器协议 5 return self 6 def __next__(self): 7 if self.n == 5: 8 raise StopIteration("终止了")#抛出异常 9 self.n+= 1 10 return self.n 11 P1 = A(1) 12 print(P1.__next__())#先调用__iter__方法,在调用__next__方法 13 print(P1.__next__()) 14 print(P1.__next__()) 15 print(P1.__next__()) 16 print(P1.__next__()) 17 for i in P1:#for本质先调用__itet__方法,变成可迭代对象,再调用__next__方法,循环打印,捕捉异常退出 18 print(i)
10. __module__和__class__
__module__ 表示当前操作的对象在那个模块
__class__ 表示当前操作的对象的类是什么
1 class A: 2 pass 3 p1 = A() 4 print(p1.__module__)#__main__#输出对象所在的模块 5 print(p1.__class__)#<class '__main__.A'>#输出对象所属的类
11.__del__
__del__:析构方法,当对象在内存中被释放时,自动触发执行。此方法一般无须定义,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。
1 class A: 2 def __init__(self,name): 3 self.name = name 4 def __del__(self): 5 print("我执行了!") 6 p1 = A("alex") 7 print(p1)#当打印完时内存释放,便会触发__del__方法
12.__call__
对象后面加括号,触发执行。
注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
1 class Foo: 2 3 def __init__(self): 4 pass 5 6 def __call__(self, *args, **kwargs): 7 8 print('__call__') 9 10 11 obj = Foo() # 执行 __init__ 12 obj() # 执行 __call__
13.描述符(__get__,__set__,__delete__)
描述符本质上就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议
__get__():调用一个属性时,触发
__set__():为一个属性赋值时,触发
__delete__():采用del删除属性时,触发
1 定义描述符 2 class Miaoshu: 3 def __get__(self, instance, owner): 4 print("这是__get__描述符") 5 def __set__(self, instance, value): 6 print("这是__set__描述符") 7 def __delete__(self, instance): 8 print("这是__delete__描述符")
描述符是干什么的:描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中),描述符只能用来代理除描述符外的其他类,
1 描述符 2 class Miaoshu: 3 def __get__(self, instance, owner): 4 print("这是__get__描述符") 5 def __set__(self, instance, value): 6 print("这是__set__描述符") 7 def __delete__(self, instance): 8 print("这是__delete__描述符") 9 class A: 10 x= Miaoshu()#A类的x属性被描述符代理了 11 def __init__(self,x): 12 self.x = x
描述符分为两种:(1)数据描述符:至少实现了__get__()和__set__()
(2)非数据描述符:没有实现__set__()
注意事项:
一 描述符本身应该定义成新式类,被代理的类也应该是新式类
二 必须把描述符定义成这个类的类属性,不能为定义到构造函数中
三 要严格遵循该优先级,优先级由高到底分别是
1.类属性
2.数据描述符
3.实例属性
4.非数据描述符
5.找不到的属性触发__getattr__()
描述符的使用:
(1)由于python是弱类型语言,类型限制
1 class Daili: 2 def __init__(self,key,expect_type): 3 self.key = key 4 self.expect_type = expect_type 5 def __get__(self, instance, owner): 6 print("触发__get__") 7 return instance.__dict__(self.key) 8 9 def __set__(self, instance, value): 10 print("触发__set__") 11 if not isinstance(value,self.expect_type): 12 raise TypeError("%s的类型不是%s"%(self.key,self.expect_type)) 13 instance.__dict__[self.key] = value 14 def __delete__(self, instance): 15 instance.__dict__.pop(self.key) 16 class A: 17 name = Daili("name",str) 18 age = Daili("age",int) 19 def __init__(self,name,age,salary): 20 self.name = name 21 self.age = age 22 self.salary = salary 23 p1 = A("alex","123",100)#由于name被代理,且代理的类属性被数据描述符代理,所以name的赋值会触发__set__方法 24 print(p1.__dict__)#由于代理中__set__没有对name进行赋值操作,所以实例p1的属性字典中不存在name这个属性
(2)实现装饰器功能
1 class Daili: 2 def __init__(self,key,expect_type): 3 self.key = key 4 self.expect_type = expect_type 5 def __get__(self, instance, owner): 6 print("触发__get__") 7 return instance.__dict__(self.key) 8 def __set__(self, instance, value): 9 print("触发__set__") 10 if not isinstance(value,self.expect_type): 11 raise TypeError("%s的类型不是%s"%(self.key,self.expect_type)) 12 instance.__dict__[self.key] = value 13 def __delete__(self, instance): 14 instance.__dict__.pop(self.key) 15 16 def zsq(**kwargs): 17 def foo(obj): 18 for key,value in kwargs.items(): 19 setattr(obj,key,Daili(key,value))#设置类的属性 20 return obj 21 return foo 22 @zsq(name = str,age = int) 23 class A: 24 name = Daili("name",str) 25 age = Daili("age",int) 26 def __init__(self,name,age,salary): 27 self.name = name 28 self.age = age 29 self.salary = salary 30 p1 = A("alex",123,100)#由于name被代理,且代理的类属性被数据描述符代理,所以name的赋值会触发__set__方法 31 print(p1.name)#由于name被代理,且代理的类属性被数据描述符代理,所以调用name属性会触发__get__方法
描述符是可以实现包括@classmethod,@staticmethd,@property甚至是__slots__属性
实现@property功能
1 class A: 2 def __init__(self,fun): 3 self.fun = fun 4 def __get__(self, instance, owner): 5 print("__get__") 6 print(instance) 7 print(self.fun.__name__) 8 setattr(instance,self.fun.__name__,self.fun(instance)) 9 return self.fun(instance)#此处的instance为Edg实例化的对象 10 class Edg: 11 def __init__(self,name,lenth,weith): 12 self.name = name 13 self.lenth = lenth 14 self.weith = weith 15 @A#相当于area = A(area),同时A本身是一个描述符,相当于area被代理了,在调用area时候会触发描述符里的__get__方法 16 def area(self): 17 return self.lenth*self.weith 18 P1= Edg("wc",10,20) 19 print(P1.area)#第一次在调用的时候由于p1属性字典里没有area属性,故会触发__get__方法,由于在__get__方法中对p1属性字典进行添加 20 # area属性操作,所以后面再次调用的时候p1会首先从自己的属性字典中查找area属性,由于之前已经设置,所以不会再触发__get__ 21 #方法,area函数也就不用再次执行,所以可以节省时间,优化了延迟计算 22 print(P1.area) 23 print(P1.area) 24 # 依次打印 25 # __get__ 26 # <__main__.Edg object at 0x0000000000A45CC0> 27 # area 28 # 200第一次打印 29 # 200 30 # 200
14.__enter__和__exit__
操作文件的方式:
1 with open(xxx,xx,xx) as f: 2 "代码块“”
上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法
1 class Open: 2 def __init__(self,name): 3 self.name = name 4 def __enter__(self): 5 return self 6 def __exit__(self, exc_type, exc_val, exc_tb): 7 print("执行__exit__!") 8 print(exc_type) 9 print(exc_val) 10 print(exc_tb) 11 return True#如果在抛出异常情况下触发__exit__方法,且返回值为True,则代表__exit__清除了异常,with函数执行结束,下面代码继续执行,否则会中断执行 12 with Open("a")as f:#with操作触发__enter__方法,__enter__方法返回值赋值给f 13 print(f) 14 print("======") 15 print("======") 16 print(ajflaj)#打印未定义的变量,触发异常,触发__exit__方法,待exit函数执行结束,代表with函数里的代码块也执行结束 17 print("=========") 18 print("*************")
用途或者说好处:
1.使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预
2.在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处
15.metaclass(元类)
python中一切皆是对象,类本身也是一个对象,当使用关键字class的时候,python解释器在加载class的时候就会创建一个对象(这里的对象指的是类而非类的实例)
元类是类的类,是类的模板
元类是用来控制如何创建类的,正如类是创建对象的模板一样
元类的实例为类,正如类的实例为对象(f1对象是Foo类的一个实例,Foo类是 type 类的一个实例)
type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象
创建类的两种方式:
第一种
1 class A: 2 pass
第二种
1 def aoo(self): 2 pass 3 x = 1 4 Foo = type("Foo",(object,),{"aoo":aoo,"x":1}) 5 print(Foo)
自定制元类
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('alex')