python面向对象进阶(下)
一、item系列:就是把字典模拟成一个字典去操作(操作字典就用item的方式)
obj[‘属性’]的方式去操作属性时触发的方法
__getitem__:obj['属性'] 时触发
__setitem__:obj['属性']=属性的值 时触发
__delitem__:del obj['属性'] 时触发
示例:
#把对象的方式模拟成字典的方式去操作
class foo:
def __init__(self,name):
self.name=name
#获取
def __getitem__(self, item):
return self.__dict__[item]
# 增加和修改操作
def __setitem__(self, key, value):
print(type(key),type(value))
self.__dict__[key]=value
#删除
def __delitem__(self, key):
self.__dict__.pop(key)
f=foo("xuyuanyuan")#实例化
print(f.name)#调用属性
f.name="tom"#将名字xuyuanyuan修改成tom
#增加和修改操作
f["age"]=18#增加
f["name"]="xuyuanyuan"#修改
print(f.__dict__)
#删除
del f["name"]
print(f.__dict__)
#获取
# print(f["name"])#获取,会触发_getitem_的运行,没有该属性会报错
执行结果是:
xuyuanyuan
<class 'str'> <class 'int'>
<class 'str'> <class 'str'>
{'name': 'xuyuanyuan', 'age': 18}
{'age': 18}
二、 __slots__()(对象不会建立自己的名称空间,全部放入类的名称空间内)
(慎用)
1.__slots__是什么?是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)
2.引子:使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的)
3.为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__ 当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个 字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__一个不好的地方就是我们不能再给 实例添加新的属性了,只能使用在__slots__中定义的那些属性名。
4.注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该 只在那些经常被使用到 的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。 关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。更多的是用来作为一个内存优化工具。
__slots__的作用:节省内存空间
示例:
# _slots_#表示的是对象不会建立自己的名称空间,全部都放入类的名称空间内,省内存,省空间
class foo:
__slots__ = ["name","age","sex"]
p=foo()
# print(p.__dict__)#会报错
print(foo.__dict__)
p.name="xuyuanyuan"
p.sex="girl"
p.age=18
print(p.name,p.age,p.sex)
print(foo.__dict__)#不会报错,全部都放入类的名称空间内
# print(p.__dict__)#会报错,对象不会建立自己的名称空间
执行结果是:
{'__module__': '__main__', '__slots__': ['name', 'age', 'sex'], 'age': <member 'age' of 'foo' objects>, 'name': <member 'name' of 'foo' objects>, 'sex': <member 'sex' of 'foo' objects>, '__doc__': None}
xuyuanyuan 18 girl
{'__module__': '__main__', '__slots__': ['name', 'age', 'sex'], 'age': <member 'age' of 'foo' objects>, 'name': <member 'name' of 'foo' objects>, 'sex': <member 'sex' of 'foo' objects>, '__doc__': None}
三、 __next__和__iter__实现迭代器协议
(1)、什么是迭代器协议
1.迭代器协议是指:对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代 (只能往后走不能往前退)
2.可迭代对象:实现了迭代器协议的对象(如何实现:对象内部定义一个__iter__()方法)
3.协议是一种约定,可迭代对象实现了迭代器协议,python的内部工具(如for循环,sum,min,max函数等)使用迭代器协议访问对象。
(2)、python中强大的for循环机制
for循环的本质:循环所有对象,全都是使用迭代器协议。
(字符串,列表,元组,字典,集合,文件对象)这些都不是可迭代对象,只不过在for循环式,调用了他们内部的__iter__方法,把他们变成了可迭代对象
然后for循环调用可迭代对象的__next__方法去取值,而且for循环会捕捉StopIteration异常,以终止迭代。
示例1:(使用next)
from collections import Iterable,Iterator#判断是否是可迭代
class foo:
def __init__(self,num):
self.num=num
def __iter__(self):
return self
def __next__(self):
if self.num>5:#如果大于5的话
raise StopIteration#抛出异常
n=self.num
self.num=self.num+1
return n
f=foo(0)#实例化
print(next(f))#====>0
print(next(f))#====>1
print(next(f))#====>2
print(next(f))#====>3
print(next(f))#====>4
print(next(f))#====>5
print(next(f))#用next的话,超出范围则会报错
执行结果是:
0
Traceback (most recent call last):
1
2
File "C:/Users/Administrator/PycharmProjects/py_fullstack_s4/day33-item系列/实现迭代器的协议.py", line 21, in <module>
3
print(next(f))#用next的话,超出范围则会报错
4
5
File "C:/Users/Administrator/PycharmProjects/py_fullstack_s4/day33-item系列/实现迭代器的协议.py", line 9, in __next__
raise StopIteration#抛出异常
StopIteration
示例2:(使用for循环)
from collections import Iterable,Iterator#判断是否是可迭代
class foo:
def __init__(self,num):
self.num=num
def __iter__(self):
return self
def __next__(self):
if self.num>5:#如果大于5的话
raise StopIteration#抛出异常
n=self.num
self.num=self.num+1
return n
f=foo(0)#实例化
for i in f:#用for循环的话,超出不会报错
print("——>",i)
执行的结果是:
——> 0
——> 1
——> 2
——> 3
——> 4
——> 5
示例3:(当for循环需要去范围时)
class range:
def __init__(self,num_start,num_stop):
self.num_start=num_start
self.num_stop=num_stop
def __iter__(self): #把一个对象变成一个可迭代对象,必须有__iter__
return self
def __next__(self):
if self.num_start==self.num_stop:#如果开始的数和结束的数相等时
raise StopIteration#抛出异常
n=self.num_start#接收最开始初始化的值
self.num_start=self.num_start+1
return n
f=range(0,3)#实例化
for i in f:
print(">>>",i)
执行结果是:
>>> 0
>>> 1
>>> 2
四、__del__,析构函数
析构方法,当对象在内存中被释放时,自动触发执行。
注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。
python有自己定时清理垃圾的习惯,但是当定义了_del_时,如果没有引用关系的话,垃圾会立即处理
示例:
import time
class Open:
def __init__(self,filepath,m='r',encode='utf-8'):
self.f=open(filepath,mode=m,encoding=encode)
def write(self,line):
pass
def __getattr__(self, item):
return getattr(self.f,item)
def __del__(self):#python有自己定时清理垃圾的习惯,但是当定义了_del_时,如果没有引用关系的话,垃圾会立即处理
print("——>del")
self.f.close()
#
f=Open('b.txt','w')
f1=f#这时候有2个引用关系,所以del在删除时不会立即执行
del f
print(">")
time.sleep(100)#睡100秒
执行结果是:
>>>>
五、__enter__和__exit__
即上下文协议
1、操作文件写法
示例:
with open("b.txt","r") as f:
"""需要的代码"""
res=f.readlines()
print(res)
执行结果是:
['1111\n', '2\n', '90918\n', '2\n', '2\n', '3']#文件b.txt内的内容
2、上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法
示例:
class Foo:
def __enter__(self):
print('=======================》enter')
return 123#返回值给下面with的文件句柄f
def __exit__(self, exc_type, exc_val, exc_tb):#下面with里面的代码执行完毕,则执行这个
print('exit')
with Foo() as f: #with Foo()是会触发def __enter__(self)这个函数的运行,并将def __enter__(self)函数的返回值拿回来赋值给f
print('with foo的自代码块',f)
执行结果是:
=======================》enter
with foo的自代码块 123
exit
3、执行代码块
__exit__()中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法执行
没有异常的情况下,整个代码块运行完毕后去触发__exit__,它的三个参数都会执行
示例:
class Foo:
def __enter__(self):
print('=======================》enter')
return 123#返回值给下面with的文件句柄f
def __exit__(self, exc_type, exc_val, exc_tb):#下面with里面的代码执行完毕,则执行这个
print('exit')
print('exc_type', exc_type)
print('exc_val',exc_val)
print('exc_tb',exc_tb)
with Foo() as f: #with Foo()是会触发def __enter__(self)这个函数的运行,并将def __enter__(self)函数的返回值拿回来赋值给f
print('with foo的自代码块',f)
print("aaaaaaaaaa")
print("bbbbbbbbb")
执行结果是:
=======================》enter
with foo的自代码块 123
aaaaaaaaaa
bbbbbbbbb
exit
exc_type None
exc_val None
exc_tb None
4、有返回值
如果__exit()返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行
示例:
class Foo:
def __enter__(self):
print('=======================》enter')
return 123 # 返回值给下面with的文件句柄f
def __exit__(self, exc_type, exc_val, exc_tb):#下面with里面的代码执行完毕,则执行这个
print('exit')
print('exc_type',exc_type)
print('exc_val',exc_val)
print('exc_tb',exc_tb)
return True#加上这个的话,则运行完毕不会报错,并且可以执行下面的代码
with Foo() as f: #with Foo()是会触发def __enter__(self)这个函数的运行,并将def __enter__(self)函数的返回值拿回来赋值给f
print('with foo的自代码块',f)
print("aaaaaaaaaa")
raise NameError('主动抛出异常')#加上这个的话,才会去触发上面的def __exit__(self, exc_type, exc_val, exc_tb)的运行,并且with里面的代码和后面的代码都不会运行,直接结束了
print('22222222222')
print('1111111111111')
执行结果是:
=======================》enter
with foo的自代码块 123
aaaaaaaaaa
exit
exc_type <class 'NameError'>
exc_val 主动抛出异常
exc_tb <traceback object at 0x000000000282BEC8>
1111111111111
总结:
with obj as f:
'代码块'
1.with obj ---->触发obj.__enter__(),拿到返回值
2.as f----->f=返回值、
3.with obj as f 等同于 f=obj.__enter__()
4.执行代码块
一:没有异常的情况下,整个代码块运行完毕后去触发__exit__,它的三个参数都为None
二:有异常的情况下,从异常出现的位置直接触发__exit__
a:如果__exit__的返回值为True,代表吞掉了异常
b:如果__exit__的返回值不为True,代表吐出了异常
c:__exit__的的运行完毕就代表了整个with语句的执行完毕
用途:
1.使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预
2.在需要管理一些资源比如文件,网络连接(TCP协议建连接、传输数据、关连接)和锁(进程,线程)的编程环境中,可以在__exit__中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处。
六、call方法
类的调用有2种方式:实例化和用.(点)的方式调用属性
对象的调用:因为对象只有数据属性,所以对象只能进行属性调用
但是call方法的话,也可以使对象加上括号运行
对象后面加括号,触发执行。
注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
示例:
class People:
def __init__(self,name):
self.name=name
def __call__(self, *args, **kwargs):#call方法,使对象可以加上括号运行
print("call")
p=People("xuyuanyuan")
print(callable(People))#判断People是否是可调用对象————true
print(callable(p))#判断实例化对象是否是可调用对象————flase
People("tom")
p()#call方法,使对象p可以加上括号触发_call_的运行
执行结果是:
True
True
call