多态
OOP相关内置函数
类中的魔法函数
描述符:属性的get set 和del
getitem,setitem,delite
对象比较大小
迭代器
上下文管理
多态
概念:一种事物具备多种不同的形态,例如水,有固态、气态、液态
官方解释:多个不同类对象可以响应同一个方法,产生不同的结果
首先强调多态不是一种特殊的语法,而是一种状态,特征(既多个不同对象可以响应同一个方法,产生不同的结果)既多个对象有相同的使用方法。
好处:对于使用者而言,大大降低了使用难度
之前写的USB接口下的,鼠标,键盘,就属于多态
# 协议:支持打开关闭,读写数据class USB: def open(self): pass def close(self): pass def read(self): pass def write(self): pass# 按USB标准制作鼠标class Mouse(USB): def open(self): # 打开方法 print("鼠标开机了") def close(self): print("鼠标关闭了") def read(self): print("获取了光标位置") def write(self): # 请忽略鼠标配置 print("鼠标可以写入灯光颜色等数据...") # 至此,Mouse就算是一个合格的USB设备了# 按USB标准制作键盘class KeyBoard(USB): def open(self): # 打开方法 print("键盘开机了") def close(self): print("键盘关闭了") def read(self): print("获取了按键字符...") def write(self): # 请忽略鼠标配置 print("键盘可以写入灯光颜色等数据...") # 至此,Mouse就算是一个合格的USB设备了# ..........其他符合USB接口协议的设备...........def pc(usb_device): usb_device.open() usb_device.read() usb_device.write() usb_device.close()mouse = Mouse()# 将鼠标传给pcpc(mouse)# 鼠标开机了# 获取了光标位置# 鼠标不支持写入数据# 鼠标关闭了key_board = KeyBoard()pc(key_board)# 键盘开机了# 获取了按键字符...# 键盘可以写入灯光颜色等数据...# 键盘关闭了# 上述过程,鼠标键盘的使用都没有改变pc 的代码(使用方式),体现了扩展性和复用性
实现多态
接口 、抽象类、鸭子类型、都可以写出具备多态的代码,最简单的就是鸭子类型
class JI: def bark(self): print("哥哥哥") def spawn(self): print("下鸡蛋..")class Duck: def bark(self): print("嘎嘎嘎") def spawn(self): print("下鸭蛋")class E: def bark(self): print("饿饿饿....") def spawn(self): print("下鹅蛋..")j = JI()y = Duck()e = E()def mange(obj): obj.spawn()mange(j)mange(y)mange(e)# python中到处都有多态 a = 10b = "10"c = [10]print(type(a))print(type(b))print(type(c))
OOP相关内置函数
isinstance:判断一个对象是否是某个类的实例
class Foo(object): pass obj = Foo() isinstance(obj, Foo)# 参数1 要判断的对象 # 参数2 要判断的类型
issubclass:判断一个类是否是另一个类的子类
class Foo(object): pass class Bar(Foo): pass issubclass(Bar, Foo)# 参数一是子类# 参数二是父类
类中的魔法函数
__str__:调用str函数或者print函数时自动执行,返回值作为显示内容
__repr__:调用repr或者交互式解释器输出对象是自动执行,返回值作为显示内容
注意:如果`__str__`没有被定义,那么就会使用`__repr__`来代替输出
这俩方法的返回值必须是字符串,否则抛出异常
__del__:执行时机: 手动删除对象时立马执行,或是程序运行结束时也会自动执行 使用场景:当你的对象在使用过程中,打开了不属于解释器的资源:例如文件,网络端口
# del使用案例# class FileTool:# """该类用于简化文件的读写操作 """ def __init__(self,path): self.file = open(path,"rt",encoding="utf-8") self.a = 100 def read(self): return self.file.read() # 在这里可以确定一个事,这个对象肯定不使用了 所以可以放心的关闭问文件了 def __del__(self): self.file.close() tool = FileTool("a.txt") print(tool.read())
__format__
调用format函数时自动执行,用于定制对象的格式化输出,
format使用案例:
#{0.year}:{0.month}:{0.day} 这是一个格式化字符串 ,想到于"%s:%s:%s" year表示取对象的year属性值date_dic={ 'ymd':'{0.year}:{0.month}:{0.day}', 'dmy':'{0.day}/{0.month}/{0.year}', 'mdy':'{0.month}-{0.day}-{0.year}',}class Date: def __init__(self,year,month,day): self.year=year self.month=month self.day=day def __format__(self, format_spec): if not format_spec or format_spec not in date_dic: format_spec='ymd' fmt=date_dic[format_spec] return fmt.format(self)d1=Date(2016,12,29)print(format(d1))print('{:mdy}'.format(d1))
__slots__:用于内存优化
字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__ 当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示,实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个字典,这跟元组或列表很类似。 在__slots__中列出的属性名在内部被映射到这个数组的指定下标上。 使用__slots__一个不好的地方就是我们不能再给实例添加新的属性了,只能使用在__slots__中定义的那些属性名。
注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该 __slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该只在那些经常被使用到 的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。 关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。更多的是用来作为一个内存优化工具。
class Foo: __slots__=['name','age']f1=Foo()f1.name='alex'f1.age=18print(f1.__slots__)#f1.y=2 报错print(f1.__slots__) #f1不再有__dict__f2=Foo()f2.name='egon'f2.age=19print(f2.__slots__)print(Foo.__dict__)#f1与f2都没有属性字典__dict__了,统一归__slots__管,节省内存
描述符
描述符本质就是一个类,在这个新式类中,至少实现了__get__,__set__,__delete__中的一个
__get__()
:调用一个属性时,触发__set__()
:为一个属性赋值时,触发__delete__()
:采用del删除属性时,触发
简单的说:描述符可以检测到一个属性的访问和修改,从而对这些操作增加额外的功能逻辑
#描述符Strclass Str: def __get__(self, instance, owner): print('Str调用') def __set__(self, instance, value): print('Str设置...') def __delete__(self, instance): print('Str删除...')#描述符Intclass Int: def __get__(self, instance, owner): print('Int调用') def __set__(self, instance, value): print('Int设置...') def __delete__(self, instance): print('Int删除...')class People: name=Str() age=Int() def __init__(self,name,age): #name被Str类代理,age被Int类代理, self.name=name self.age=age#何地?:定义成另外一个类的类属性#何时?:且看下列演示p1=People('alex',18)#描述符Str的使用p1.namep1.name='egon'del p1.name#描述符Int的使用p1.agep1.age=18del p1.age#我们来瞅瞅到底发生了什么print(p1.__dict__)print(People.__dict__)#补充print(type(p1) == People) #type(obj)其实是查看obj是由哪个类实例化来的print(type(p1).__dict__ == People.__dict__)# 描述符应用 以及执行时机
描述符的分类
1.数据描述符
至少实现了__get__()
和__set__()
两个方法
class Foo: def __set__(self, instance, value): print('set') def __get__(self, instance, owner): print('get')
2.非数据描述符
没有实现__set__()
方法
class Foo: def __get__(self, instance, owner): print('get')
注意事项
一 描述符本身应该定义成新式类,被代理的类也应该是新式类二 必须把描述符定义成这个类的类属性,不能为定义到构造函数中三 要严格遵循该优先级,优先级由高到底分别是 1.类属性 2.数据描述符 3.实例属性 4.非数据描述符 5.找不到的属性触发__getattr__():使用点__setattr__添加属性后使用点__getattr__获取属性
操作对象属性时自动触发
__setattr__
使用点语法添加/修改属性会触发它的执行
__delattr__
使用点语法删除属性的时候会触发
__getattr__
使用点语法调用属性且属性不存在的时候才会触发
__getattribute__
使用点语法调用属性的时候触发,无论属性是否存在都会执行
注意:当__getattribute__
与__getattr__
同时存在时,仅执行__getattribute__
class Foo: x=1 def __init__(self,y): self.y=y def __getattr__(self, item): print('----> from getattr:你找的属性不存在') def __setattr__(self, key, value): print('----> from setattr') # self.key=value #这就无限递归了,你好好想想 # self.__dict__[key]=value #应该使用它 def __delattr__(self, item): print('----> from delattr') # del self.item #无限递归了 self.__dict__.pop(item)#__setattr__添加/修改属性会触发它的执行f1=Foo(10)print(f1.__dict__) # 因为你重写了__setattr__,凡是赋值操作都会触发它的运行,你啥都没写,就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值f1.z=3print(f1.__dict__)#__delattr__删除属性的时候会触发f1.__dict__['a']=3#我们可以直接修改属性字典,来完成添加/修改属性的操作del f1.aprint(f1.__dict__)#__getattr__只有在使用点调用属性且属性不存在的时候才会触发f1.xxxxxx#三者的用法演示
操作对象属性时自动触发
__setitem__
使用key的形式添加/修改属性时触发
__getitem__
使用key的形式获取属性时触发
__delitem__
使用key的形式删除属性时触发
class Foo: def __init__(self,name): self.name=name def __getitem__(self, item): print(self.__dict__[item]) def __setitem__(self, key, value): self.__dict__[key]=value def __delitem__(self, key): print('del obj[key]时,我执行') self.__dict__.pop(key) def __delattr__(self, item): print('del obj.key时,我执行') self.__dict__.pop(item)f1=Foo('sb')f1['age']=18f1['age1']=19del f1.age1del f1['age']f1['name']='alex'print(f1.__dict__)
__module__和__class__
__module__
表示当前操作的对象在那个模块__class__
表示当前操作的对象的类是什么
class C: def __init__(self): self.name = 'SB'#该类位于lib/aa.py文件中
from lib.aa import Cobj = C()print obj.__module__ # 输出 lib.aa,即:输出模块print obj.__class__ # 输出 lib.aa.C,即:输出类
对象比较大小
class Student(object): def __init__(self,name,height,age): self.name = name self.height = height self.age = age def __gt__(self, other): # print(self) # print(other) # print("__gt__") return self.height > other.height def __lt__(self, other): return self.height < other.height def __eq__(self, other): if self.name == other.name and self.age == other.age and self.height == other.height: return True return Falsestu1 = Student("jack",180,28)stu2 = Student("jack",180,28)# print(stu1 < stu2)print(stu1 == stu2)# 上述代码中,other指的是另一个参与比较的对象,# 大于和小于只要实现一个即可,符号如果不同 解释器会自动交换两个对象的位置
迭代器协议
迭代器是指具有__iter__和__next__的对象
我们可以为对象增加这两个方法来让对象变成一个迭代器class MyRange: def __init__(self,start,end,step): self.start = start self.end = end self.step = step def __iter__(self): return self def __next__(self): a = self.start self.start += self.step if a < self.end: return a else: raise StopIteration for i in MyRange(1,10,2): print(i)
上下文管理之__enter__
和__exit__
上下文指的是一种语境,属于语言科学,说起来很抽象,其实你已经在很多地方使用到他了,来看一个实例:
with open('a.txt') as f: print(f.read())
在这个代码中python解释器分析出你的代码想要做的事情,然后在结束的时候自动帮你将资源释放了,with中的所有代码都在一个上下文中,你可以把他理解为一个代码范围
如何使用
该协议包含两个方法
__enter__
出现with
语句,对象的__enter__
被触发,有返回值则赋值给as声明的变量
__exit__
with中代码块执行完毕时执行
只要这个一个类实现了这两个方法就可以被with 语句使用
class Open: def __init__(self,filepath,mode='r',encoding='utf-8'): self.filepath=filepath self.mode=mode self.encoding=encoding def __enter__(self): # print('enter') self.f=open(self.filepath,mode=self.mode,encoding=self.encoding) return self.f def __exit__(self, exc_type, exc_val, exc_tb): # print('exit') self.f.close() return True def __getattr__(self, item): return getattr(self.f,item)with Open('a.txt','w') as f: print(f) f.write('aaaaaa') f.wasdf #抛出异常,交给__exit__处理
需要注意的是:
1.__exit__()
中的三个参数分别代表异常类型,异常值和追溯信息
2.with语句中代码块出现异常时,会立即触发方法__exit__
的执行,并将异常信息错误参数传入
3.with语句中代码块未出现异常正常结束时也会触发方法__exit__
的执行,此时参数中的异常信息为空
4.如果__exit__()
返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行
总结:
1.使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预
2.在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处