Python——面向对象进阶二:
一、类的内置函数补充
1、isinstance(obj,cls)——检查obj是否是该类的对象
class Hoo:
def __init__(self,name,tem):
self.name = name
self.tem = tem
class foo(Hoo):
pass
f1=foo('e',20)
print(isinstance(f1,Hoo))
首先 f1 肯定是 foo 的对象,如果 Hoo 是 foo 的父类,那么 f1 也同样是 Hoo 的对象。
2、issubclass(sub,super)——检查sub类是否是super类的派生类(子类)
3、__getattribute __
首先 __getattribute __ 与前面的 __getattr __ 很像,当然他们实际的功能也类似:
可以看到,当执行不存在的方法时,应该是执行 __getattr __ 的,但是现在执行的却是 __getattribute __ ;接着我们看下,如果执行的是存在的方法呢?
可以看到,当执行的方法是存在的时候,依然执行的是 __getattribute __ 。所以,无论执行的方法存不存在,都会执行 __getattribute __ 。
那么这个方法有什么作用呢?跟__getattr __ 又有什么联系呢?首先先来看下,当执行一个不存在的方法时,其报错的异常
这是原本的异常。
在__getattribute __方法中,可以通过 raise AttributeError 来发出你想要发出的异常
如果此时 __getattribute __ 与 __getattr __ 同时存在会发生什么呢?
从以上结果可以看到,当这两个方法同时存在,且执行类里面没有的方法时,是不会发出异常的,本该出现的异常被 __getattr __接去了,
4、__setitem __ 、__getitem __ 与 __delitem __
这三个与前面的 attr 函数很类似,功能也差不多。直接上结论。
attr 函数主要是通过 点 的方式触发,当通过 点 来调用方法时,就会触发 attr 函数。而 item 函数主要是通过字典来触发,当用字典来操作的时候,就会触发 item 函数
从上面的结果可以印证上面的结论
如果要完成本来的功能,看下:
def __setitem__(self, key, value):
self.__dict__[key] = value
def __getitem__(self, item):
return self.__dict__[item]
def __delitem__(self, key):
self.__dict__.pop(key)
5(1)、__str __ ——打印显示方式
f1 是类的实例化对象,直接打印这个对象,会显示这么一串东西。如果我们想要显示别的行不行?
修改此 str 就可以显示自己要显示的东西
5(2)、__repr __ ——打印显示方式
可以看到这里 repr 也能显示自定义打印方式
同样是打印显示方式,str 与 repr 有什么区别呢?
repr 主要在解释器中触发
那么 repr 与 str 同时存在会打印哪个?
两个同时存在的情况下,调用的是 str ,但并不是优先执行谁,而是 print 本质调用的是 str方法,如果 找不到 str ,就会去找 repr 这个替代品
注意:str 与 repr 返回值必须是字符串,否则会抛出异常
6、自定制 format 方法
方法:略
主要通过运用字典的方式来自定制
7、slots 属性
用法举例:
此方法用得少,用的时候慎用
8、__doc __ ——文档描述信息
该属性无法被继承
9、__module __和__class __ ——查看对象来自于哪一个模块或类
10、__del __ ——析构方法
结论:从上面三个结果中看出,只有实例被删除的时候才会触发 __del __ ;单单删除实例下的属性不会触发;文件执行完毕,实例被回收,触发__del __。
11、__call __
foo 虽然是一个类,但是也是一个对象(既然是对象,就有另外一个类来产生)
12(1)、__iter __ 与 __next __ ——迭代器协议
其它的暂略… …
12(2)、迭代器协议实现斐波那契数列
13、__enter __ 和 __exit __ ————上下文管理协议
首先,先来看下文件处理操作
with open('abc.txt',w) as f:
''代码''
上述代码叫做上下文管理协议,即with语句,为了让对象兼容with语句,必须在这个对象的类中声明__enter __ , 和 __exit __ 方法。先来看一下这两个方法的执行顺序
上图中,可以看到执行 with 语句的过程:先执行了 __enter __ 方法,然后输出横杠,with 里面的 print 语句执行完了,接着执行了__exit __ 方法,到此,with 语句就执行完毕了。最后就是输出等号。
如果在 __enter __ 方法中加一个 return:
class foo:
def __init__(self,name):
self.name = name
def __enter__(self):
print('执行enter')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('执行exit')
with foo('a.txt') as f:
print('-------')
print('-------')
print('-------')
那么其执行顺序就是:foo 实例化,接着执行 enter 方法,return 返回值 给 f (f 就是 foo 产生的对象)
接着来看__exit __ 方法,其后面还有三个值。先看下图
可以看到,报出异常后,直接就执行了 exit 方法(执行到这里就停止了,后面的不执行);exc_type 意为错误类型,exc_val 意为错误信息,exc_tb 意为追踪信息(这个好像没啥用)。
如果不想看到异常,正常结束 with,就在 exit 里加个 return True。
上图中,可以看到异常被吞掉了,出错的后面部分没有执行,但是 with 这里属于正常结束。接着就是执行 with 外面的语句。
总结:
那么问题来了,其用途在哪?或者说好处?:
二、描述符(__get __、__set __、__delete __)
普通开发中用不到,开发大型的框架,给别人用的时候才用得到
1、描述符的定义
2、描述符的作用
描述符的作用:是用来代理另外一个类的属性(必须把描述符定义成这个类的类属性,不能定义到构造函数中)
从上图中看到,无论怎么调用,都无法触发里面的方法,问题在于,描述符是代理另外一个类的属性,所以,必须要其他的类来调用,才能触发。看下图:
上图中看到,必须是有其他的类来调用描述符,才能触发描述符方法
3、两种描述符
描述符分两种
注意事项:
一、描述符本身应该定义成新式类,被代理的类也应该是新式类
二、必须把描述符定义成这个类的类属性,不能定义为构造函数中
比如上图这个,定义成这种就不行,什么都不会触发。再看下图:
这种是可以触发的。在上图 类hoo 当中,x被 foo 描述了,所以凡是关于 x 的操作都是 foo的操作 ;x被 foo 代理了。
三、要严格遵循该优先级,优先级由高到低分别是:
1.类属性
2.数据描述符
3.实例属性
4.非数据描述符
5.找不到的属性触发__getattr__()
举例子:
1、类属性与描述符
上图中,可以看到有描述符,有类。但是结果并不是显示描述符;因为 hoo 调用了自己的数据属性,此时显示的是 hoo 自己的属性,而描述符没有显示。所以优先级 类属性>描述符,实质是底层字典的覆盖操作,覆盖了描述符
2、描述符与实例属性
优先级:描述符>实例属性
3、实例属性与非数据描述符
没有实现 set 方法的就是数据描述符
优先级:实例属性>非数据描述符
4、描述符的应用
通过描述符来为python加上类型检测
由于在实例化对象之前,就要检测类型,所以选择数据描述符,因为数据描述符的优先级高于实例属性。
先来看下 set 方法与 get 方法后面两个值代表什么:
上图中:可以看到,instance 代表的是 p1, value 代表的是值 ,owner 代表的是该实例的拥有者(类)。但是发现似乎没有name,因为 name 被代理了,被Type的对象 Type()代理了,就是Type里面的self;所以 name 实际存储在 instance 里面。
这时候可以看到,people类实例化前,都要绕到Type类里面,原因在于真正实例化之前,可以进行判断类型检测,如果不符合,则不实例化,符合了才实例化。更多代码实现细节看上图。