Py修行路 python基础 (十九)面向对象进阶(下)
item系列 __slots__方法 __next__ 和 __iter__实现迭代器 析构函数 上下文管理协议 元类
一、item系列 把对象操作属性模拟成字典的格式。
例如:对象名['key'] = value
class Foo: def __init__(self,name): self.name = name def __getitem__(self, item): return self.__dict__[item] def __setitem__(self, key, value): self.__dict__[key] = value def __delitem__(self, key): self.__dict__.pop(key) f = Foo('egon') f['age'] = 18 print(f.__dict__) del f['age'] print(f.__dict__) print(f['name']) #print(f['sex']) #执行结果: {'name': 'egon', 'age': 18} {'name': 'egon'} egon
二、__slots__方法:
1.__slots__是什么: 是一个类变量,变量值可以是列表,元组,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)
2.使用点来访问属性本质,就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的)
3.为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__
当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不再是为每个实例定义一个字典,这跟元组或列表很类似。
__slots__中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__一个不好的地方就是我们不能再给实例添加新的属性了,只能使用在__slots__中定义的那些属性名。
4.注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该只在那些经常被使用到的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。
5、好处:节省内存:类内部指定属性,对象就只能建立所对应的属性。不再有属性字典__dict__,统一归__slots__管。
关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。更多的是用来作为一个内存优化工具。
class People: __slots__ = ['x','y','z'] p = People() print(People.__dict__) #查看类的字典,找到方法! p.x = 1 p.y = 2 p.z = 3 print(p.x,p.y,p.z) p1 = People() p1.x = 10 p1.y = 20 p1.z = 30 print(p1.x,p1.y,p1.z) print(p1.__dict__) #会报错! #执行结果: {'__module__': '__main__', '__slots__': ['x', 'y', 'z'], 'x': <member 'x' of 'People' objects>, 'y': <member 'y' of 'People' objects>, 'z': <member 'z' of 'People' objects>, '__doc__': None} 1 2 3 10 20 30 Traceback (most recent call last): File "F:/py_fullstack_s4/day32/__slots__的方法.py", line 18, in <module> print(p1.__dict__) AttributeError: 'People' object has no attribute '__dict__'
三、__next__ 和 __iter__实现迭代器
from collections import Iterable,Iterator class Foo: def __init__(self,start): self.start = start def __iter__(self): #可迭代方法,将对象变成迭代器,故返回其自己 return self def __next__(self): #对迭代器取值 if self.start > 10: raise StopIteration n = self.start self.start += 1 return n f = Foo(0) print(isinstance(Foo,Iterable)) #判断是否是可迭代对象 print(isinstance(Foo,Iterator)) #判断是否是迭代器 print(isinstance(f,Iterable)) #判断是否是可迭代对象 print(isinstance(f,Iterator)) #判断是否是迭代器 for i in f : print(i) #执行结果: False False True True 0 1 2 3 4 5 6 7 8 9 10
定义类,实现range()的方法!
class Range: def __init__(self,start,end): self.start = start self.end = end def __iter__(self): return self def __next__(self): if self.start == self.end: raise StopIteration n = self.start self.start += 1 return n for i in Range(0,3): print(i) #执行结果: 0 1 2
callable() 判断类是否可调用
四、 __doc__描述信息,该属性无法被继承,是对本函数或类的描述!没写打印None
__class__和__module__
__module__ 表示当前操作的对象在哪个模块
__class__ 表示当前操作的对象的类是什么
class Foo: pass class A(Foo): pass a = A() print(a.__class__) print(a.__module__) #执行结果: <class '__main__.A'> __main__
五、__del__ 析构函数
删除对象或是对象执行完回收,引用基数为0,的时候,自动触发执行,将对象在内存中释放。
注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。
在类中定义 __del__ 一般是写入文件关闭,或是数据库关闭的这种终止操作。只要是函数没有完全运行结束,就不会将内存全部回收,执行完谁回收谁。
import time class Open: def __init__(self,filepath,mode = 'r',encode = 'utf-8'): self.f = open(filepath,mode=mode,encoding=encode) def write(self): pass def __getattr__(self, item): return getattr(self.f,item) def __del__(self): print('=---->del') self.f.close() f = Open('a.txt','r+') #没有删除命令,也会自动执行!当没有代码要执行,引用基数为0的时候,就会触发。 # f1 = f #定义其他的引用 # del f #先执行删除命令,再打印下边的内容 print('=-====>') #执行方式: # =-====> # =---->del #先执行删除命令,再打印下边的内容 del f #先执行删除命令,再打印下边的内容 print('=-====>') #没有删除命令,也会自动执行!当没有代码要执行,引用基数为0的时候,就会触发。 #执行方式: # =---->del # =-====> #引用基数不为0的情况 f1 = f #定义其他的引用 del f #先执行删除命令,再打印下边的内容 print('=-====>') #执行方式: # =-====> # =---->del
六、上下文管理协议 (with 方法)
__enter__ 和__exit__
用途或者说好处:
1.使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预
2.在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处
# __enter__和__exit__ class Foo: def __enter__(self): #with 拿到一个对象,触发enter方法 print('enter') return 11111 def __exit__(self, exc_type, exc_val, exc_tb): #文件打开执行完毕后,执行exit方法 print('exit') print('exc_type',exc_type) #异常的类型 print('exc_val',exc_val) #异常的值 print('exc_tb',exc_tb) #异常的内存地址 return True #清空所有异常,抛异常之后,with后的内容正常执行。 with Foo() as obj: #执行Foo().__enter__方法得到一个返回值,然后将这个值赋给obj。 #出现with语句, 对象的__enter__被触发, 有返回值则赋值给as声明的变量 print('with Foo 的子代码块',obj) #执行with模块字代码 raise NameError('名字没有定义!') #只要是报异常,没有处理的话,就意味着with执行的字代码块运行完。触发exit方法,这之后的代码就不会再执行。 print('##############') print('**************') #执行结果: enter with Foo 的子代码块 11111 exit exc_type <class 'NameError'> exc_val 名字没有定义! exc_tb <traceback object at 0x00000000028EE948> **************
例子:实现上下文的管理协议:(with方法打开文件执行操作!)
class Open: def __init__(self,filepath,mode,encode='utf-8'): self.f=open(filepath,mode=mode,encoding=encode) self.filepath=filepath self.mode=mode self.encoding=encode def write(self,line): print('write') self.f.write(line) def __getattr__(self, item): return getattr(self.f,item) def __enter__(self): #with 对象 就会触发对象下的该方法 return self #将对象返回 write_file=Open('aaaaa.txt','w') #return self.f #这就是返回真实的open方法,字代码块的方法都可以使用,但是就不再是类Open的使用。 def __exit__(self, exc_type, exc_val, exc_tb):#文件结束清理 self.f.close() return True with Open('aaaaa.txt','w') as write_file: #变相的实例化 write_file=Open('aaaaa.txt','w')拿到产生的文件句柄 write_file.write('123123123123123\n') write_file.write('123123123123123\n') write_file.write('123123123123123\n')
七、__call__方法
对象后面加括号,触发执行。
注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
class Foo: def __init__(self,name): self.name = name def __call__(self, *args, **kwargs): print('__call__') obj = Foo('egon') # 执行 __init__ #obj() # 执行 __call__ 对象对去找有没有__call__的绑定方法,有加()就能运行! #查看是否为可调用对象(可调用对象:名字后加()就能运行) print(callable(Foo)) print(callable(obj)) #执行结果: True True #若是隐去__call__功能,查看结果 class Foo: def __init__(self,name): self.name = name # def __call__(self, *args, **kwargs): # print('__call__') obj = Foo('egon') # 执行 __init__ #obj() # 执行 __call__ 对象对去找有没有__call__的绑定方法,有加()就能运行! #查看是否为可调用对象(可调用对象:名字后加()就能运行) print(callable(Foo)) print(callable(obj)) #执行结果: True False
八、元类
1 引子
1 class Foo: 2 pass 3 4 f1=Foo() #f1是通过Foo类实例化的对象
python中一切皆是对象,类本身也是一个对象,当使用关键字class的时候,python解释器在加载class的时候就会创建一个对象(这里的对象指的是类而非类的实例)
上例可以看出f1是由Foo这个类产生的对象,而Foo本身也是对象,那它又是由哪个类产生的呢?
1 #type函数可以查看类型,也可以用来查看对象的类,二者是一样的 2 print(type(f1)) # 输出:<class '__main__.Foo'> 表示,obj 对象由Foo类创建 3 print(type(Foo)) # 输出:<type 'type'>
2 什么是元类?
元类是类的类,是类的模板
元类是用来控制如何创建类的,正如类是创建对象的模板一样
元类的实例为类,正如类的实例为对象(f1对象是Foo类的一个实例,Foo类是 type 类的一个实例)
type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象
3 创建类的两种方式
方法一:
class Foo: x =1 def run(self): pass print(Foo) print(type(Foo))
方法二:
#type称为元类,是所有类的类,控制类的。利用type模拟class关键字的创建类的过程。 def run(self): print('%s is running'%self.name) class_name = 'Bar' #类名 bases=(object,) #继承方式 class_dic = {'x':1,'run':run} #名称空间 属性方法 #语法:变量名 = type(类名,继承关系(元组),属性方法(字典)) class 关键字创建类,其实质就是type()封装的方法! Bar = type(class_name,bases,class_dic) #自定义生成一个类 print(Bar) #查看类型 print(type(Bar)) #查看元类
4 一个类没有声明自己的元类,默认他的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元类(顺便我们也可以瞅一瞅元类如何控制类的创建,工作流程是什么)
(1)通过元类给类添加一些条件判断方法(查看工作流程)
class Mymeta(type): #定义元类继承type def __init__(self,class_name,class_bases,class_dic): #实例化就会调用执行默认的方法,和用type直接创建一个类一致。(类名,继承关系,方法) print(self) #对比打印验证 print(class_name) #对比打印验证 print(class_bases) #对比打印验证 print(class_dic) #对比打印验证 #对创建类,不写__doc__方法的人进行提示 for key in class_dic: #可在元类初始化这里加上对下边 子类 的判断验证方法 if not callable(class_dic[key]):continue #判断类方法中有没有可调用方法,没有继续 if not class_dic[key].__doc__: #判断方法类中__doc__对应的值是否为None raise TypeError('小子,你没写注释,赶紧去写') # type.__init__(self,class_name,class_bases,class_dic) #执行的上述方法实际上就是在调用type()的方法 class Foo(metaclass=Mymeta):#将类foo看成元类的对象,加()运行就是在进行实例化 x=1 def run(self): 'run function' print('running') # Foo=Mymeta('Foo',(object,),{'x':1,'run':run})#上述利用class方法定义Foo类与用type()定义一致。 print(Foo.__dict__) #执行结果: <class '__main__.Foo'> Foo () {'__module__': '__main__', '__qualname__': 'Foo', 'x': 1, 'run': <function Foo.run at 0x00000000022BC950>} {'__module__': '__main__', 'x': 1, 'run': <function Foo.run at 0x00000000022BC950>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None}
(2)利用元类,定义产生一个类的完整过程:
class Mymeta(type): #定义元类 def __init__(self,class_name,class_bases,class_dic):#始终明确 self 就是Foo pass #不定义就走type()的内部方法 def __call__(self, *args, **kwargs): #类实例化就能调用执行其实质上是执行了__call__方法 # print(self) obj=self.__new__(self) #类Foo实例化f, Foo执行__new__方法,先产生一个空对象 self.__init__(obj,*args,**kwargs) #obj.name='egon' #上一行代码是在 给产生的空对象传值,调用Foo下的__init__方法,也就是定义的类 Foo中的__init__方法 return obj #空对象拿到值之后,产生一个新的对象obj,然后获取这个值,将这个值返回。 class Foo(metaclass=Mymeta): x=1 def __init__(self,name): #类内给实例化对象定义的初始属性 self.name=name #obj.name='egon' def run(self): 'run function' print('running') # print(Foo.__dict__) f=Foo('egon') #类实例化 print(f) print(f.name) #执行结果: <__main__.Foo object at 0x0000000002280128> egon
注意点:
#元类总结 class Mymeta(type): def __init__(self,name,bases,dic): print('===>Mymeta.__init__') def __new__(cls, *args, **kwargs): print('===>Mymeta.__new__') return type.__new__(cls,*args,**kwargs) def __call__(self, *args, **kwargs): print('aaa') obj=self.__new__(self) self.__init__(self,*args,**kwargs) return obj class Foo(object,metaclass=Mymeta): def __init__(self,name): self.name=name def __new__(cls, *args, **kwargs): return object.__new__(cls) ''' 需要记住一点:名字加括号的本质(即,任何name()的形式),都是先找到name的爹,然后执行:爹.__call__ 而爹.__call__一般做两件事: 1.调用name.__new__方法并返回一个对象 2.进而调用name.__init__方法对儿子name进行初始化 ''' ''' class 定义Foo,并指定元类为Mymeta,这就相当于要用Mymeta创建一个新的对象Foo,于是相当于执行 Foo=Mymeta('foo',(...),{...}) 因此我们可以看到,只定义class就会有如下执行效果 ===>Mymeta.__new__ ===>Mymeta.__init__ 实际上class Foo(metaclass=Mymeta)是触发了Foo=Mymeta('Foo',(...),{...})操作, 遇到了名字加括号的形式,即Mymeta(...),于是就去找Mymeta的爹type,然后执行type.__call__(...)方法 于是触发Mymeta.__new__方法得到一个具体的对象,然后触发Mymeta.__init__方法对对象进行初始化 ''' ''' obj=Foo('egon') 的原理同上 ''' ''' 总结:元类的难点在于执行顺序很绕,其实我们只需要记住两点就可以了 1.谁后面跟括号,就从谁的爹中找__call__方法执行 type->Mymeta->Foo->obj Mymeta()触发type.__call__ Foo()触发Mymeta.__call__ obj()触发Foo.__call__ 2.__call__内按先后顺序依次调用儿子的__new__和__init__方法 '''