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()
posted @ 2017-06-20 13:04  秋名山藤原豆腐哥  阅读(237)  评论(0编辑  收藏  举报