面向对象进阶---slots,迭代器协议,上下文管理协议,元类
一 item系列
__setitem__,__getitem,__delitem__ 用法
#把对象操作属性模拟成字典的格式 class Foo: def __init__(self,name): self.name=name def __getitem__(self, item): print("---get---",self,item) print(self.__dict__[item]) def __setitem__(self, key, value): # print("---set---",self,key,value) self.__dict__[key]=value def __delitem__(self, key): print("---del---",self,key) # self.__dict__.pop(key) def __delattr__(self, item): print("---delattr---",self,item) # self.__dict__.pop(item) #把对象操作属性模拟成字典的格式 f=Foo("SB") # print(f.name) f["age"]=18 #字典方式增加属性 # f.__dict__["age"]=19 #简单粗暴的增加属性,这样的话就不需要__setitem__这个功能了 # f.age=20 #这就单纯的直接增加自己属性了,上面的item方法都没卵用了。 # print(f) # print(f.__dict__) # del f.age #这种删除方法会触发__delattr__的运行删除相应的属性 # print(f.__dict__) # del f["age"] #这种删除方法会触发__delitem__的运行删除相应的属性 f["age"] #字典方式查询的话会触发__getitem__代码运行,从f名称空间找key对应的属性。 # print(f.__dict__)
二 关于__slots__
""" 1.__slots__是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性) 2.引子:使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的) 3.为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__ 当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个 字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__一个不好的地方就是我们不能再给 实例添加新的属性了,只能使用在__slots__中定义的那些属性名。 4.注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该 只在那些经常被使用到 的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。 关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。 更多的是用来作为一个内存优化工具。 """ class People: __slots__=["x","y","z"] p=People() print(People.__dict__) # print(p.__dict__)#查看p的属性字典会报错,因为对象已经没有了__dict__,这就是__slots__的特点 p.x=1 p.y=2 p.z=3 print(p.x,p.y,p.z)#没错,这几个属性确实存在,但是没在p的属性字典里 # print(p.__dict__) print(People.__dict__)#p的属性字典存在它的类的属性空间里,但是我看不到1,2,3这几个数值。 p1=People() #实例化另一个对象 p1.x=10 p1.y=20 p1.z=30 print(p1.x,p1.y,p1.z) #它的属性也是存在类的属性字典里,跟p互不干涉。 print(People.__dict__) #我还是看不到它的属性值 #p与p1都没有属性字典__dict__了,统一归__slots__管,节省内存
三 __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>5: raise StopIteration #当next超值时主动抛出异常 n=self.start #将初始值保留在n中 self.start+=1 #自加一,实现next取值效果 return n #将取得的最新值返回 # return self.start f=Foo(0) f.__iter__() print(type(Foo)) # iter(Foo) # next(Foo) print(isinstance(Foo,Iterable)) #判断类是否为可迭代对象,类在这里不是。 print(isinstance(Foo,Iterator))#同上 # print(next(f)) # print(next(f)) # print(next(f)) # print(next(f)) # print(next(f)) # print(next(f)) # print(next(f)) print(isinstance(f,Iterable)) print(isinstance(f,Iterator)) #定义一个类实现range的特性 class Range: def __init__(self,start,stop): self.start=start self.stop=stop def __iter__(self): # print("---iter---") return self def __next__(self): # print("--next--") if self.start==self.stop: raise StopIteration n=self.start self.start+=1 return n # for i in Range(0,4): # print(i) print(isinstance(Range,Iterator)) print(isinstance(Range,Iterable))
四 __doc__介绍,以及__module__和__class__的方法
__module__ 表示当前操作的对象在那个模块
__class__ 表示当前操作的对象的类是什么
# __doc__是查看描述信息的方法 class Foo: "我是描述信息" pass class Bar(Foo): pass print(Bar.__doc__)#输出显示None -- > 该属性无法继承父类Foo的。 print(Foo.__doc__) #输出 --> 我是描述信息
class C: def __init__(self): self.name="SB" a=C() print(a.__module__) #输出模块(来自哪个模块) print(a.__class__) #输出类(来自哪个类)
五 析构函数
析构方法,当对象在内存中被释放时,自动触发执行。
注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。
class Foo: def __del__(self): print('执行我啦') f1=Foo() del f1 print('------->') #输出结果 执行我啦 ------->
class Foo: def __del__(self): print('执行我啦') f1=Foo() # del f1 print('------->') #输出结果 -------> 执行我啦 #为何啊???
六 __enter__和__exit__
操作文件时的常用代码:
with open("a.txt") as f: "代码块"
上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法
class Open: def __init__(self,name): self.name=name def __enter__(self): print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量') # return self def __exit__(self, exc_type, exc_val, exc_tb): print('with中代码块执行完毕时执行我啊') with Open('a.txt') as f: print('=====>执行代码块') # print(f,f.name)
1.__exit__()中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法执行
class Open: def __init__(self,name): self.name=name def __enter__(self): print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量') def __exit__(self, exc_type, exc_val, exc_tb): print('with中代码块执行完毕时执行我啊') print(exc_type) print(exc_val) print(exc_tb) with Open('a.txt') as f: print('=====>执行代码块') raise AttributeError('***着火啦,救火啊***') print('0'*100) #------------------------------->不会执行
2.如果__exit()返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行
class Open: def __init__(self,name): self.name=name def __enter__(self): print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量') def __exit__(self, exc_type, exc_val, exc_tb): print('with中代码块执行完毕时执行我啊') print(exc_type) print(exc_val) print(exc_tb) return True with Open('a.txt') as f: print('=====>执行代码块') raise AttributeError('***着火啦,救火啊***') print('0'*100) #------------------------------->会执行
3.定义(授权)open函数并实现上下文管理协议
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): return self #返回self就是做了x=Open("aaa.txt","w")的操作 def __exit__(self, exc_type, exc_val, exc_tb): self.f.close() # 利用with代码执行完后自动执行__exit__代码,将文件关闭函数close加入其中实现 #with as 的功能 with Open("aaa.txt","w") as x: x.write("1111111111\n") x.write("1111111111\n") print("--------------------")
用途或者说好处:
1.使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预
2.在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处
七 初识元类
class Foo: pass f=Foo() #f是通过Foo类实例化的对象
1.python中一切皆是对象,类本身也是一个对象,当使用关键字class的时候,python解释器在加载class的时候就会创建一个对象(这里的对象指的是类而非类的实例)
上面的f是由Foo这个类产生的对象,而Foo本身也是对象,那么它又是谁产生的呢?
#type函数可以查看类型,也可以用来查看对象的类,二者是一样的 print(type(f)) #输出:<class "__main__.Foo"> 表示:obj对象由Foo类创建 print(type(Foo)) #输出: <type "type">
2 什么是元类?
元类是类的类,是类的模板
元类是用来控制如何创建类的,正如类是创建对象的模板一样
元类的实例为类,正如类的实例为对象(f对象是Foo类的一个实例,Foo类是 type 类的一个实例)
type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象
3 创建类的两种方式
方式一:
class Foo: def func(self): print("from func")
方式二:
def func(self): print("from func") x=1 Foo=type("Foo",(object,),{"func":func,"x":1})
4 一个类没有声明自己的元类,默认他的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元类(顺便我们也可以瞅一瞅元类如何控制类的创建,工作流程是什么)
class Mytype(type): def __init__(self,class_name,bases=None,dict=None): print("Mytype init--->") print(class_name,type(class_name)) print(bases) print(dict) def __call__(self, *args, **kwargs): print('Mytype call---->',self,args,kwargs) obj=self.__new__(self) self.__init__(obj,*args,**kwargs) return obj class Foo(object,metaclass=Mytype):#in python3 #__metaclass__ = MyType #in python2 x=1111111111 def __init__(self,name): self.name=name def __new__(cls, *args, **kwargs): return super().__new__(cls) # return object.__new__(cls) #同上 f1=Foo('name') print(f1.__dict__)
class Mytype(type): def __init__(self,what,bases=None,dict=None): print('mytype init') def __call__(self, *args, **kwargs): obj=self.__new__(self) self.__init__(obj,*args,**kwargs) return obj class Foo(object,metaclass=Mytype): x=1111111111 def __init__(self,name): self.name=name def __new__(cls, *args, **kwargs): return super().__new__(cls) f1=Foo('egon') print(f1.__dict__)
class Mytype(type): def __init__(self,what,bases=None,dict=None): print(what,bases,dict) def __call__(self, *args, **kwargs): print('--->') obj=object.__new__(self) self.__init__(obj,*args,**kwargs) return obj class Room(metaclass=Mytype): def __init__(self,name): self.name=name r1=Room('alex') print(r1.__dict__)
#元类总结 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__方法 '''
参考大神egon的博客:http://www.cnblogs.com/linhaifeng/articles/6204014.html