Python 基础 四 面向对象杂谈
一、isinstance(obj,cls) 与issubcalss(sub,super)
isinstance(obj,cls)检查是否obj是否是类 cls 的对象 class Foo: pass f1=Foo() print(isinstance(f1,Foo)) #True issubclass(sub, super)检查sub类是否是 super 类的派生类 class Foo: pass class Xoo(Foo): pass print(issubclass(Xoo,Foo)) #True
二、__getattribute__
在介绍__getattribute__之前是否还记得之前学过一个叫__getattr__的方法,这两者之间是否存在某种关系呢?实际上呢,还是有关系的,是个什么情况呢,就是大哥和小弟的关系,有大哥在的时候大哥上,大哥不在小弟上,或者大哥不想干了,让给了小弟,那你小弟就必须上了是吧。
先看一下__getattr__的例子:
class Foo: def __init__(self,x): self.x=x def __getattr__(self, item): print('执行的是我') # return self.__dict__[item] f1=Foo(10) print(f1.x) #d当访问的属性存在的时候是不会触发__getattr__ #f1.xxxxxx #不存在的属性访问,触发__getattr__,若自己没有重写此方法时,就会触发系统默认的此方法,若重写了就会按你自己写的东西执行
好了,再看一下两个都存在的触发方式及结果:
class Foo: def __init__(self,x): self.x=x def __getattr__(self, item): print('执行的是getattr') # return self.__dict__[item] def __getattribute__(self, item): print('执行的是getattribute') raise AttributeError('抛出异常了') # raise TabError('xxxxxx') f1=Foo(10) #f1.x f1.xxxxxx #不存在的属性访问,触发__getattr__,若有__getattribute__就先触发此方法(不管属性是否存在),若后面有raise方法抛出异常的时候,就会再次交给__getattr__处理
三、item系列
看到这里是否还记得之前说过一个系列叫做:attr,这两者又是什么关系?我们先看看item系列
class Foo: def __getitem__(self, item): print('getitem',item) #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) f1=Foo() #print(f1.__dict__) #对象f1的属性字典是空的————》{} #f1.name='egon'#---->setattr-------->f1.__dict__['name']='egon' #print(f1.__dict__) #{'name': 'egon'} f1['name']='egon'#--->setitem--------->f1.__dict__['name']='egon' f1['age']=18 #会触发 内部的__setitem__方法 # print('===>',f1.__dict__) # ===> {}查看属性字典却没有添加进去,只是触发了内部的__setitem__方法,若要添加上,就必须对底层进行操作: self.__dict__[key]=value # 这样就把属性字典添加上了 ===> {'name': 'egon', 'age': 18} del f1.name # 删除属性值 print(f1.__dict__) # {'age': 18} # # print(f1.age) del f1['name'] #触发__delitem__,若要真实的删除属性字典中的值就必须对底层进行操作: self.__dict__.pop(key) #print(f1.__dict__)
总结一下item系列的触发是通过字典类型的方式操作完成的,而attr方式的触发是通过对象加点(.)的方式触发的,这就是两者的最大区别,但是两者最终的效果是一样的。
四 、 _str_ ;_reper_;__format__
_str_ ;_reper_;改变对象的字符串显示方式换句话就是展示print()函数的 执行结果的:
class Foo: def __init__(self,name,age): self.name=name self.age=age def __str__(self): return '名字是%s 年龄是%s' %(self.name,self.age) # # f1=Foo('egon',18) print(f1) #str(f1)--->f1.__str__() 若没有__str__函数,则执行print()就会执行提供的默认的打印内容 #比如本实例中打印的内容为:<__main__.Foo object at 0x006166D0>,若我们在定义的时候写了__str__函数就会按照我们自己定义好的方式执行打印操作:名字是egon 年龄是18 # x=str(f1) print(x) # y=f1.__str__() print(y)
class Foo: def __init__(self,name,age): self.name=name self.age=age def __str__(self): return '折是str' def __repr__(self): return '名字是%s 年龄是%s' %(self.name,self.age) f1=Foo('egon',19) #repr(f1)---->f1.__repr__() print(f1) #str(f1)---》f1.__str__()------>f1.__repr__()
总结:当 str 与repr同时存在的时候执行顺序是:先找str 若没有再去找repr,若都没有就去执行默认的print()打印结果。
__format__ 自定义方式来制定输出格式:
format_dic={ 'ymd':'{0.year}{0.mon}{0.day}', 'm-d-y':'{0.mon}-{0.day}-{0.year}', 'y:m:d':'{0.year}:{0.mon}:{0.day}' } class Date: def __init__(self,year,mon,day): self.year=year self.mon=mon self.day=day def __format__(self, format_spec): print('我执行啦') print('--->',format_spec) if not format_spec or format_spec not in format_dic: format_spec='ymd' fm=format_dic[format_spec] return fm.format(self) d1=Date(2016,12,26) format(d1) #d1.__format__() print(format(d1)) print(format(d1,'ymd')) print(format(d1,'y:m:d')) print(format(d1,'m-d-y')) print(format(d1,'m-d:y')) print('===========>',format(d1,'asdfasdfsadfasdfasdfasdfasdfasdfasdfasd
五 、__slots__ (省内存)
1.__slots__是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)
2.引子:使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的)
3.为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__
class Foo: __slots__=['name','age'] #{'name':None,'age':None} # __slots__='name' #{'name':None,'age':None} f1=Foo() f1.name='egon' #print(f1.name) f1.age=18 #--->setattr----->f1.__dict__['age']=18 #print(f1.__dict__) #AttributeError: 'Foo' object has no attribute '__dict__' 为何会出现这个结果,这就是不让各个实例对象自己封存自己的数据属性了。 print(Foo.__slots__) #['name', 'age']类中指存放了键的属性 print(f1.__slots__) # ['name', 'age'] f1.name='egon' f1.age=17 print(f1.name) print(f1.age) f1.gender='male' #Foo' object has no attribute 'gender' 在定义数据属性的时候就没有定义性别的属性,so就加不进去
六、__doc__
class Foo: '我是描述信息' pass print(Foo.__doc__) # 我是描述信息
需要注意的是:此属性是不具有继承的:
class Foo: '我是描述信息' pass class Bar(Foo): pass print(Bar.__doc__) #该属性无法继承给子类 打印结果为:None
七、__module__和__class__
_module__ 表示当前操作的对象在那个模块
__class__ 表示当前操作的对象的类是什么
八、__del__
析构方法,当对象在内存中被释放时,自动触发执行。
class Foo: def __del__(self): print('执行我啦') f1=Foo() del f1 print('------->') #输出结果 执行我啦 ------->
九 、__call__方法
对象后面加括号,触发执行。
注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
class Foo: def __init__(self): print('你就是大傻逼') def __call__(self, *args, **kwargs): print('__call__') obj = Foo() # 执行 __init__ obj() # 执行 __call__
十、用 __next__和__iter__实现面向对象的迭代器协议
class Foo: def __init__(self,n): self.n=n def __iter__(self): return self def __next__(self): if self.n == 13: raise StopIteration('终止了') self.n+=1 return self.n # l=list('hello') # for i in l: # print(i) f1=Foo(10) print(f1.__next__()) print(f1.__next__()) print(f1.__next__()) print(f1.__next__()) for i in f1: # obj=iter(f1)------------>f1.__iter__() #此时的f1的值为最后一次迭代额值 print(i) #obj.__next_()
十一、描述符(__get__,__set__,__delete__)(这个东西很高大上,不过现在的级别还是没啥卵用,就先简单的了解一下下)
1 描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议
__get__():调用一个属性时,触发
__set__():为一个属性赋值时,触发
__delete__():采用del删除属性时,触发
一个小小的例子:
class Foo: #在python3中Foo是新式类,它实现了三种方法,这个类就被称作一个描述符 def __get__(self, instance, owner): pass def __set__(self, instance, value): pass def __delete__(self, instance): pass
2 描述符是干什么的:描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)
#描述符Str class Str: def __get__(self, instance, owner): print('Str调用') def __set__(self, instance, value): print('Str设置...') def __delete__(self, instance): print('Str删除...') #描述符Int class 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.name p1.name='egon' del p1.name #描述符Int的使用 p1.age p1.age=18 del p1.age #我们来瞅瞅到底发生了什么 print(p1.__dict__) print(People.__dict__) #补充 print(type(p1) == People) #type(obj)其实是查看obj是由哪个类实例化来的 print(type(p1).__dict__ == People.__dict__) 描述符应用之何时?何地?
3 描述符分两种
一 数据描述符:至少实现了__get__()和__set__()
1 class Foo: 2 def __set__(self, instance, value): 3 print('set') 4 def __get__(self, instance, owner): 5 print('get')
二 非数据描述符:没有实现__set__()
1 class Foo: 2 def __get__(self, instance, owner): 3 print('get')
4 注意事项:
一 描述符本身应该定义成新式类,被代理的类也应该是新式类
二 必须把描述符定义成这个类的类属性,不能为定义到构造函数中
三 要严格遵循该优先级,优先级由高到底分别是
1.类属性
2.数据描述符
3.实例属性
4.非数据描述符
5.找不到的属性触发__getattr__()
今天的主要内容就这些,看起来还不错,实践,实践,还是实践!!!