面向对象进阶
反射:python中面向对象的反射,是通过字符串的形式去调用对象(类和对象)的属性
好处一:实现可插拔机制
有俩程序员,一个lili,一个是egon,lili在写程序的时候需要用到egon所写的类,但是egon去跟女朋友度蜜月去了,还没有完成他写的类,lili想到了反射,使用了反射机制lili可以继续完成自己的代码,等egon度蜜月回来后再继续完成类的定义并且去实现lili想要的功能。
总之反射的好处就是,可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种‘后期绑定’,什么意思?即你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能
class FtpClient: 'ftp客户端,但是还么有实现具体的功能' def __init__(self,addr): print('正在连接服务器[%s]' %addr) self.addr=addr
#from module import FtpClient f1=FtpClient('192.168.1.1') if hasattr(f1,'get'): func_get=getattr(f1,'get') func_get() else: print('---->不存在此方法') print('处理其他的逻辑')
好处二:动态导入模块(基于反射当前模块成员)
1.hasattr,getattr,setattr,delattr的使用
1.hasattr 查看对象是否有该属性 class Foo: def __init__(self,name,age): self.name = name self.age = age def read(self): print('123123123') f = Foo('deng',18) print(hasattr(f,'name')) print(hasattr(f,'read')) print(hasattr(f,'read1')) print(hasattr(Foo,'read')) >>>>>>> True True False True 2.getattr 通过字符串的形式取到对象的属性 print(getattr(f,'read')) 取到的是绑定方法,因为对象是实例 print(getattr(Foo,'read')) 取到的是函数属性,因为对象是类 s = getattr(f,'read') s() 绑定方法会自动传值 s1 = getattr(Foo,'read') s1('1') 是函数属性就要传值 >>>>>>> <bound method Foo.read of <__main__.Foo object at 0x000001A97A9C0DD8>> <function Foo.read at 0x000001A97A9B99D8> 123123123 123123123 3.setattr 修改 print(f.name) print(f.age) setattr(f,'name','yuan') setattr(f,'age','22') print(f.name) print(f.age) >>>>>>>> deng 18 yuan 22 4.delattr 删除 print(f.name) print(f.__dict__) delattr(f,'name') print(f.__dict__) >>>>>> deng {'age': 18, 'name': 'deng'} {'age': 18}
2.attr系列:
class FOO: def __init__(self,name,age): self.name = name self.age = age def __setattr__(self, key, value): #当__init__内有对象属性赋值时,会触发__setattr__的运行 if not isinstance(value,str): raise TypeError('输入类型错误,必须是字符串!') self.__dict__[key] = value def __delattr__(self, item): #删除 del self.__dict__[item] def __getattr__(self, item): #1.当对象属性存在的情况下,不触发 2.当对象执行没有的属性时会触发__getattr__的运行 print('%s的属性不存在!' % item) f1 = FOO('deng','18') print(f1.name,f1.age) f1.name = 'yuan' f1.age = '19'
f1.age = 11
>>>>>>>>>>>>>> #提示输入数据类型: f1.age = 11
File "C:/Users/dsy/PycharmProjects/mianxianduixiang/面向对象进阶/attr系列.py", line 10, in __setattr__
raise TypeError('输入类型错误,必须是字符串!')
TypeError: 输入类型错误,必须是字符串!
print(f1.name,f1.age) del f1.name print(f1.__dict__) f1.sex #sex属性不存在,触发__getattr__运行
>>>>>>>>>
deng 18
yuan 19
{'age': '19'}
sex的属性不存在!
3.数据的二次加工,定义自己的数据类型:
有两种方式:1.基于类的方式定义自己的数据类型
2.基于授权的方式定义自己的数据类型
#1.定义属于自己的list功能: # 基于继承定义自己的数据类型 class List(list): def append(self, p_object): if not isinstance(p_object,int): raise TypeError('Must be int!') super().append(p_object) def insert(self, index, p_object): if not isinstance(index,int) or not isinstance(p_object,int): raise TypeError('Index or p_object must be int!') super().insert(index,p_object) @property def mid(self): #定义属于自己的新的功能 index = len(self)//2 return self[index] l = List([1,2,3,4,5]) print(l) res = l.mid print(res) l.append(1) print(l) l.append('123') l.insert(0,'123') l.insert('1',1) print(l) >>>>>>>> [1, 2, 3, 4, 5] File "C:/Users/dsy/PycharmProjects/mianxianduixiang/面向对象进阶/加工定制自己的数据类型.py", line 24, in <module> 3 l.append('123') [1, 2, 3, 4, 5, 1] File "C:/Users/dsy/PycharmProjects/mianxianduixiang/面向对象进阶/加工定制自己的数据类型.py", line 8, in append raise TypeError('Must be int!') TypeError: Must be int! # 2定义自己的open()函数,模拟日志 # 基于授权定义自己的数据类型: import time class Open: def __init__(self,file_path,mode='r',encoding='utf-8'): self.w = open(file_path,mode=mode,encoding=encoding) self.file_path = file_path self.mode = mode self.encoding = encoding def write(self,line): time.sleep(2) t = time.strftime('%Y-%m-%d %X') self.w.write('%s %s' %(t,line)) def __getattr__(self, item): print(getattr(self.w,item)) return getattr(self.w,item) f = Open('a.txt','w') f.write('222222\n') f.write('222222\n') f.write('222222\n') f.write('222222\n') f = Open('a.txt') res =f.read() f.close() res1 =f.read() >>>>>>>>> <built-in method read of _io.TextIOWrapper object at 0x00000123CF57EB40> Traceback (most recent call last): <built-in method close of _io.TextIOWrapper object at 0x00000123CF57EB40> File "C:/Users/dsy/PycharmProjects/mianxianduixiang/面向对象进阶/加工定制自己的数据类型.py", line 54, in <module> <built-in method read of _io.TextIOWrapper object at 0x00000123CF57EB40> res1 =f.read() ValueError: I/O operation on closed file.
4._solts_:
''' 1.__slots__是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性) 2.引子:使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的) 3.为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__ 当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个 字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__一个不好的地方就是我们不能再给 实例添加新的属性了,只能使用在__slots__中定义的那些属性名。 4.注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该 只在那些经常被使用到 的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。 关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。 更多的是用来作为一个内存优化工具。 ''' class Foo: __slots__='x' f1=Foo() f1.x=1 f1.y=2#报错 print(f1.__slots__) #f1不再有__dict__ class Bar: __slots__=['x','y'] n=Bar() n.x,n.y=1,2 n.z=3#报错
class Foo: __slots__=['name','age'] f1=Foo() f1.name='alex' f1.age=18 print(f1.__slots__) f2=Foo() f2.name='egon' f2.age=19 print(f2.__slots__) print(Foo.__dict__) #f1与f2都没有属性字典__dict__了,统一归__slots__管,节省内存
5.实现迭代器协议:
from collections import Iterable,Iterator class Foo: def __init__(self,start,stop): self.start = start self.stop = stop def __iter__(self): return self def __next__(self): if self.start > self.stop: raise StopIteration count = self.start self.start += 1 return count f = Foo(0,10) print(isinstance(f,Iterable)) print(isinstance(f,Iterator)) for i in f: print(i)
>>>>>>>>
True
True
0
1
2
3
4
5
6
7
8
9
10
6.实现上下问管理协议:
class Open:#模拟with open as 的使用 def __init__(self,file_path,mode='r',encoding='utf-8'): self.file_path = file_path self.mode = mode self.encoding = encoding self.w = open(file_path,mode=mode,encoding=encoding) def wirte(self,line): self.w.write(line) def __getattr__(self, item): return getattr(self.w,item) def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self.w.close() with Open('b.txt','w') as file_write: file_write.wirte('1223\n') file_write.wirte('1223\n') file_write.wirte('1223\n') file_write.wirte('1223\n') file_write.wirte('1223\n') >>>>>>>>>>>
'''
1.__slots__是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)
2.引子:使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的)
3.为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__
当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个
字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__一个不好的地方就是我们不能再给
实例添加新的属性了,只能使用在__slots__中定义的那些属性名。
4.注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该
只在那些经常被使用到 的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。
关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。 更多的是用来作为一个内存优化工具。
'''
class Foo:
__slots__='x'
f1=Foo()
f1.x=1
f1.y=2#报错
print(f1.__slots__) #f1不再有__dict__
class Bar:
__slots__=['x','y']
n=Bar()
n.x,n.y=1,2
n.z=3#报错
__slots__使用