Python自动化开发课堂笔记【Day08】 - Python进阶(面向对象的高级用法,网络编程)
面向对象的高级用法
1. __str__
只要执行打印对象的操作,就会触发该对象类中的__str__方法(也就是对象的绑定方法)
它是一种默认的方法,默认的打印输出为<__main__.Foo object at 0x003EE350>,但是如果将该绑定方法
在类中重写的话,要求必须有以字符串类型的返回值,返回形式可以自己设定。
class Foo: def __init__(self,name,age): self.name = name self.age = age def __str__(self): return 'name:%s age:%d' % (self.name,self.age)#返回值必须有 obj=Foo('Albert',18) print(obj)
2. __del__(析构函数)
由类产生的对象是存放在内存中的,程序结束后要释放掉对象,则会触发__del__方法执行
class Foo: def __init__(self,name,age): self.name = name self.age = age def __del__(self): #注意:必须是程序执行结束后才会执行该方法 print('__del__') obj=Foo('Albert',18) del obj #此时是主动触发执行__del__方法 print('after __del__')
3. __setitem__, __getitem__, __delitem__
利用字典的方式来操作对象的属性
#方法修改之前: class Foo: def __init__(self,name): self.name = name def __getitem__(self, item): print('getitem') def __setitem__(self, key, value): print('setitem') def __delitem__(self, key): print('delitem') obj = Foo('egon') print(obj.__dict__) #{'name': 'egon'} obj['name'] = 'Albert' #虽然可以调用__setitem__方法,但是无法完成真正的修改操作 print(obj.__dict__) #{'name': 'egon'} obj.__dict__['name'] = 'Albert' #正确的修改方式 print(obj.__dict__) #{'name': 'Albert'} #方法修改之后: class Foo: def __init__(self,name): self.name = name def __getitem__(self, item): print('getitem') 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) obj = Foo('egon') obj['name'] = 'Albert' #调用__setitem__ print(obj['name']) #调用__getitem__ del obj['name'] #调用__delitem__ print(obj.__dict__) #结果:{},空字典,属性被删除
4. __getattr__,__setattr__,__delattr__
class Foo: def __init__(self,x): self.x = x def __getattr__(self, item): print('getattr') def __setattr__(self, key, value): print('setattr') self.__dict__[key] = value def __delattr__(self, item): print('delattr') self.__dict__.pop(item) # obj=Foo() # obj.x = 1 #触发__setattr__,但未执行成功 # print(obj.__dict__) # del obj.x #触发__delattr__,但未执行成功 # print(obj.__dict__) # print(obj.x) #触发__getattr__ obj = Foo(10) #触发__setattr__ print(obj.x) #没有触发__getattr__ print(obj.__dict__) print(obj.y) #当属性不存在的时候才会触发__getattr__ del obj.x #触发__delattr__ print(obj.x) #触发__getattr__,说明x已经被删除
二次加工标准类型
1. 继承
需要改写的类型是一个类,可以通过继承的方式实现
需求:改写list规定只能加入字符串类型数据 class List(list): class List(list): def __init__(self,item_list,tag=False): super().__init__(item_list) self.tag = tag def append(self, p_object): if not isinstance(p_object,str): #判断要加入的元素是否是字符串,非字符串元素会报错 raise TypeError('must be str') else: super().append(p_object) #继承父类的方法 @property def mid_num(self): mid_index = len(self) // 2 return self[mid_index] def clear(self): if not self.tag: raise PermissionError('not allowed')#查看是否有清除列表权限 super().clear() #继承父类的方法 self.tag = False l = List([1,2,3]) l.append('a') print(l) print(l.mid_num) l.tag = True l.clear() print(l)
2. 授权
针对你需要改写的类型它不是一个类,无法用继承的方式实现,只能用授权的方式实现
import time class Open: def __init__(self,filepath,mode='r',encoding='utf-8'): self.filepath = filepath self.mode = mode self.encoding = encoding self.ff = open(self.filepath,mode=self.mode,encoding=self.encoding) def write(self,msg): t = time.strftime('%Y-%m-%d %X') self.ff.write('%s %s' % (t,msg)) def __getattr__(self, item): return getattr(self.ff, item) obj = Open('a.txt','w',encoding='utf-8') #未重写write方法时调用方式 # obj.ff.write('111\n') # obj.ff.write('222\n') # obj.ff.write('333\n') # obj.ff.close() #重写write方法后的调用方式 obj.write('aaa\n') obj.write('bbb\n') obj.write('ccc\n') print(obj.seek) #<built-in method seek of _io.TextIOWrapper object at 0x0056A530> obj.close()
3. __next__和__iter__实现迭代器协议
class Foo: def __init__(self,n_start,n_stop): self.n_start = n_start self.n_stop = n_stop def __next__(self): if self.n_start >= self.n_stop: raise StopIteration #遍历到最后报出异常
x = self.n_start self.n_start += 1 return x def __iter__(self): return self obj = Foo(0,10) # print(next(obj)) # print(next(obj)) # print(next(obj)) for i in obj: print(i) #相当于 for i in range(10): print(i)
4. __enter__和__exit__实现上下文管理协议
class Open: def __init__(self,name,mode='w',encoding='utf-8'): self.name = name self.mode = mode self.encoding = encoding self.f = open(self.name,mode=self.mode,encoding=self.encoding) def __enter__(self): print('__enter__') return self.f def __exit__(self, exc_type, exc_val, exc_tb): #整个代码块结束触发 print('__exit__') # print('exc_type',exc_type) #异常类型 # print('exc_val',exc_val) #异常的值 # print('exc_tb',exc_tb) #异常的追踪信息 self.f.close() # return True #处理异常,保证异常所处子代码块以外的代码正常进行 obj = Open('b.txt','w')#没有文件的话会自动创建 print(obj) #<__main__.Open object at 0x002559F0> with Open('a.txt') as f: #with Open('a.txt')操作的结果就是触发__enter__返回self.f, 之后as f相当于f=self.f print(f) #<_io.TextIOWrapper name='a.txt' mode='w' encoding='utf-8'> # 1/0 f.write('333\n')
5. __call__方法
class Foo: def __call__(self, *args, **kwargs): print('===>') obj=Foo() obj() #如果类内部没有定义__call__方法,对象是不能以加括号的方式调用的。
网络编程
1. socket是什么?
socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,socket其实就是一个门面模式,
它把复杂的TCP/IP协议族隐藏在socket接口后面,对用户来说,一组简单的接口就是全部,让socket去组织数据,以符合
指定的协议。我们无需深入理解TCP/UDP协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出
的程序自然就是遵循TCP/UDP标准的。
2. 基于TCP协议的socket的简单实现
Server端实现 import socket #socket.AF_INET 指定套接字地址家族 #socket.SOCK_STREAM 指TCP流式协议 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.bind(('127.0.0.1',8080)) #绑定IP地址端口号 phone.listen(5) #bind连接池 #conn为三次握手成功后建立的连接 #addr为客户端的地址 conn,addr = phone.accept() #等待连接 print('conn',conn) print('client addr',addr) client_msg = conn.recv(1024) #收消息 print('clent msg: %s' % client_msg) conn.send(client_msg.upper()) #发送消息 conn.close() #关闭连接 phone.close() #关闭通信 Client端实现 import socket phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect(('127.0.0.1', 8080)) #客户端发起连接 phone.send('Hello'.encode('utf-8')) #发消息 back_msg = phone.recv(1024) print(back_msg) phone.close()
3. 通信循环和连接循环
Server端实现 import socket #socket.AF_INET 指定套接字地址家族 #socket.SOCK_STREAM 指TCP流式协议 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) phone.bind(('127.0.0.1',8080)) #绑定IP地址端口号 phone.listen(5) #bind连接池 while True:#连接循环 # conn为三次握手成功后建立的连接 # addr为客户端的地址 conn, addr = phone.accept() # 等待连接 print('conn', conn) print('client addr', addr) while True: # 与conn的通信循环 try: client_msg = conn.recv(1024) # 收消息 if not client_msg: break #针对Linux平台,收空内容后断开客户端连接,windows平台下可不写 print('clent msg: %s' % client_msg) conn.send(client_msg.upper()) # 发送消息 except Exception: # 解决服务端的异常终止 break conn.close() # 关闭连接 phone.close() #关闭通信 Client端实现 import socket phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect(('127.0.0.1', 8080)) #客户端发起连接 while True:#通信循环 msg = input('>>>:').strip() if not msg: continue #解决客服端发送空内容的问题 phone.send(msg.encode('utf-8')) #发消息 back_msg = phone.recv(1024) print(back_msg) phone.close()
4. 基于socket实现远程执行shell命令
Server端实现 import socket import subprocess #socket.AF_INET 指定套接字地址家族 #socket.SOCK_STREAM 指TCP流式协议 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) phone.bind(('127.0.0.1',8080)) #绑定IP地址端口号 phone.listen(5) #bind连接池 while True:#连接循环 # conn为三次握手成功后建立的连接 # addr为客户端的地址 conn, addr = phone.accept() # 等待连接 print('conn', conn) print('client addr', addr) while True: # 与conn的通信循环 try: cmd = conn.recv(1024) # 收消息 if not cmd: break res = subprocess.Popen(cmd.decode('utf-8'),shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) err = res.stderr.read() if err: cmd_res = err else: cmd_res = res.stdout.read() conn.send(cmd_res) except Exception: # 解决服务端的异常终止 break conn.close() # 关闭连接 phone.close() #关闭通信 Client端实现 import socket phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect(('127.0.0.1', 8080)) #客户端发起连接 while True:#通信循环 cmd = input('>>>:').strip() if not cmd: continue #解决客服端发送空内容的问题 phone.send(cmd.encode('utf-8')) #发消息 cmd_res = phone.recv(1024) print(cmd_res.decode('gbk')) phone.close()
自定义包头解决粘包问题
P.S.只有TCP有粘包现象,UDP永远不会粘包
什么是粘包?
所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。这取决于TCP的工作原理。
粘包发生在客户端:受限于网络传送介质的速度,未来得及每条都及时发送给服务端,导致发送的数据在客户端的缓存堆积并一块送到服务端
粘包发生在服务端:服务端的接收的数据在缓存中未及时被取完,导致接下来从客户端发送过来的数据堆积在服务端的缓存,下一次可能被一并取出
CPU工作的两种状态
内核态:运行操作系统,可以操作硬件
用户态:运行用户的应用程序
如何解决粘包的问题:
需要自己定制报头让发送端在发送数据之前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有的数据。
Server端实现 import socket import subprocess import struct import json #socket.AF_INET 指定套接字地址家族 #socket.SOCK_STREAM 指TCP流式协议 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) phone.bind(('127.0.0.1',8080)) #绑定IP地址端口号 phone.listen(5) #bind连接池 while True:#连接循环 # conn为三次握手成功后建立的连接 # addr为客户端的地址 conn, addr = phone.accept() # 等待连接 print('conn=', conn) print('client addr=', addr) while True: # 与conn的通信循环 try: cmd = conn.recv(1024) # 收消息 if not cmd: break res = subprocess.Popen(cmd.decode('utf-8'),shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) err = res.stderr.read() if err: cmd_res = err else: cmd_res = res.stdout.read() # conn.send(struct.pack('i',len(cmd_res))) #先发报头 head_dict={'filename':None,'hash':None,'total_size':len(cmd_res)} head_json = json.dumps(head_dict) #报头信息序列化 head_bytes = head_json.encode('utf-8')#将报头信息转化为字节形式传输 conn.send(struct.pack('i',len(head_bytes))) #发送报头长度 conn.send(head_bytes) #再发送报头数据 conn.send(cmd_res) #再发真实的数据 except Exception: # 解决服务端的异常终止 break conn.close() # 关闭连接 phone.close() #关闭通信 Client端实现 import socket import struct import json phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect(('127.0.0.1', 8080)) #客户端发起连接 while True:#通信循环 cmd = input('>>>:').strip() if not cmd: continue #解决客服端发送空内容的问题 phone.send(cmd.encode('utf-8')) #发消息 head_len_info = phone.recv(4) #收到报头的长度信息 head_len = struct.unpack('i', head_len_info)[0] #得到报头的长度 head_bytes = phone.recv(head_len) #获取报头信息 head_json = head_bytes.decode('utf-8') #报头信息反序列化 head_dict = json.loads(head_json) #获取报头字典格式 total_size = head_dict['total_size'] #从字典中取出真实的数据 # total_size = struct.unpack('i',head)[0] recv_size = 0 data = b'' while recv_size < total_size: recv_data = phone.recv(1024) data += recv_data recv_size += len(recv_data) print(data.decode('gbk')) phone.close()