• 封装,多态,反射,异常处理,网络编程


    接口与归一化设计(python中使用继承的方式)

    抽象类

    import abc
    class Interface(metaclass=abc.ABCMeta): #定义接口Interface类来模仿接口的概念,python中#没有interface关键字来定义一个接口
        all_type='file'
        #只定义功能集合而不实现具体的功能
        @abc.abcstractmethod
        def read(self): # 定义接口函数read
            pass
        @abc.abcstractmethod
        def write(self): # 定义接口函数write
            pass
    
    class Txt(Interface): #文本,具体实现read和write 
        def read(self):
            print('文本数据的读取方法')
        def write(self):
            print('文本数据的写方法')
    
    t=Txt()
    print(t.all_type)

    多态

    一种接口,多种实现(接口重用)

    class Animal:
        def __init__(self,name):
            self.name=name
    
        def talk(self):
            pass
    
        @staticmethod
        def animal_talk(obj):
            obj.talk()
    
    class Cat(Animal):
        def talk(self):
            print('Meow!')
    
    class Dog(Animal):
        def talk(self):
            print('Woof!Woof!')
    
    d = Dog('chen')
    #d.talk()
    
    c = Cat('xu')
    #c.talk()
    
    使用统一的接口
    Animal.animal_talk(c)
    Animal.animal_talk(d)

    封装

    1.如何隐藏属性(类的属性和对象的属性)

    class Foo:
        __N=1111 # 类的属性被隐藏 _Foo__N:1111
        def __init__(self,name):
            self.__Name=name # 给对象属性隐藏 self._Foo__Name=name
    
        def __f1(self): #_Foo__f1
            print('f1')
    
        def f2(self):
            self.__f1() # self._Foo__f1()
    
    f=Foo('egon')
    #print(f.__N) # 报错,无法访问
    #f.__f1() # 也无法访问
    #f.__Name #也无法访问
    f.f2() # f1 内部调用,可以访问

    #以上隐藏需要注意的问题:

    #1.这种隐藏只是一种语法上变形操作,并不会将属性真正隐藏起来

    #print(Foo.__dict__)类的名称空间 => '-Foo__N':1111
    #print(f.__dict__)对象的名称空间 => {'_foo_Name':'egon'}
    #print(f._foo_Name) # egon
    #print(f._Foo__N) # 1111

    #2.这种语法级别的变形是在类定义阶段发生,并且只在类定义阶段发生

    Foo.__x=2222
    print(Foo.__dict__) # '__x':2222
    print(Foo.__x) # 2222
    f.__x=3333
    print(f.__dict__) # '__x':3333 并没有发生变形
    print(f.__x) # 3333

    #3.在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,

    #而父类中变形成了:_父类名__x,即双下划线开头的属性在继承给子类时,子类是无法覆盖的

    class Foo:
        __N=1111 # _Foo__N
        def __init__(self,name):
            self.__Name=name # self._Foo__name=name
    
        def __f1(self): # _Foo__f1
            print('f1')
    
        def f2(self):
            self.__f1() # self._Foo__f1()
    
    
    f=Foo('egon')
    #print(f.__N)
    #f.__f1()
    #f.__Name
    #f.f2()

    封装的真实目的

    #封装不是单纯意义上的隐藏
    #1.封装数据属性:将属性隐藏起来,然后对外提供访问属性的接口,关键是我们在接口内定制一些控制逻辑
    #从而严格控制使用对数据属性的使用

    class People:
        def __init__(self,name,age):
            self.__Name=name
            self.__Age=age
    
        def tell_info(self):
            print('<名字:%s 年龄:%s>' % (self.__Name,self.__Age))
    
        def set_info(self,x,y):
            if not isinstance(x,str):
                raise TypeError('%s must be str' %x) 
            if not isinstance(y,int):
                raise TypeError('%s must be int' %y)
            self.__Name=x
            self.__Age=y
    
    p=People('egon',18)
    p.tell_info()
    
    #p.set_info('Egon','19')
    p.set_info('Egon',19)
    p.tell_info()

    #2.封装函数属性:为了隔离复杂度

    class ATM:
        def __card(self):
            print('插卡')
        def __auth(self):
            print('用户认证')
        def __input(self):
            print('输入取款金额')
        def __print_bill(self):
            print('打印账单')
        def __take_money(self):
            print('取款')
    
        def withdraw(self):
            self.__card()
            self.__auth()
            self.__input()
            self.__print_bill()
            self.__take_money()
    
    a=ATM()
    a.withdraw()

    静态属性property

    # bmi 体脂指数
    class People:
        def __init__(self,name,weight,height):
            self.name=name
            self.weight=weight
            self.height=height
    
        @property
        def bmi(self):
            return self.weight / (self.height**2)
    
    p=People('egon',75,1.80)
    #print(p.bmi())
    #bmi是一个名词,像访问数据属性一样访问bmi就可以了
    print(p.bmi)
    
    #访问,设置,删除
    class Foo:
        def __init__(self,x):
            self.__Name=x
    
        @property
        def name(self):
            return self.__Name
    
        @name.setter # 前提是name已经被property修饰过一次了,才能调用.setter
        def name(self,val):
            if not isinstance(val,str):
                raise TypeError
            self.__Name=val
    
        @name.deleter
        def name(self):
            #print('===')
            #del self.__Name
            raise PermissionError # 不允许删除
    
    f=Foo('egon')
    print(f.name)
    f.name='Egon'
    print(f.name)
    del f.name
    print(f.name)

    反射

    #通过字符串找到属性对应的值

    class Foo:
        x=1
        def __init__(self,name):
            self.name=name
    
        def f1(self):
            print('from f1')
    
    f=Foo('egon')
    
    #hasattr
    print(hasattr(f,'name')) # f.name
    print(hasattr(f,'f1')) # f.f1
    print(hasattr(f,'x')) # f.x
    
    #setattr
    setattr(f,'age',18) # f.age=18
    
    #getattr
    print(getattr(f,'name')) # f.name
    print(getattr(f,'abc',None)) # f.abc
    print(getattr(f,'name',None)) # f.abc
    
    func=getattr(f,'f1') # f.f1
    print(func)
    func()
    
    #delattr
    delattr(f,'name') # del f.name
    print(f.__dict__)
    class FtpServer:
    
        def __init__(self,host,port):
            self.host=host
            self.port=port
    
        def run(self):
            while True:
            cmd=input('>>: ').strip()
                if not cmd:continue
                if hasattr(self,cmd):
                    func=getattr(self,cmd)
                    func()
        def get(self):
            print('get func')
    
        def put(self):
            print('put func')
    
    f=FtpServer('192.168.1.2',21)
    f.run()

    item系列
    #把对象变成类似字典对象,可以像字典一样进行增删改查
    #也是一种归一化的思想

    #__setitem__,__getitem__,__delitem__
    
    class Foo:
        def __getitem__(self,item):
            print('getitem--->',self,item)
            return self.__dict__[item]
    
        def __setitem__(self,key,value):
            print('setitem--->',self,key,value)
                self.__dict__[key]=value
                # setattr(self,key,value) 效果一样
    
        def __delitem__(self,key):
            print('delitem--->',self,key)
                self.__dict__.pop(key)
    
    f=Foo()
    f['x']=1111
    del f['x']
    print(f['x'])

    打印对象信息__str__

    class People:
        def __init__(self,name,age,sex):
            self.name=name
            self.age=age
            self.sex=sex
    
        def __str__(self): #在对象被打印时触发执行
            return '<name:%s age:%s sex:%s>' % (self.name,self.age,self.sex) #只能返回字符串类型
    
    p=People('egon',18,'male')
    
    print(p)

    析构方法__del__

    class Foo:
        def __init__(self,x):
            self.x=x
    
        def __del__(self): # 在对象资源被释放时出发
            print('---del---')
    
    f=Foo()
    print('===>')

    异常处理

    #什么时候用try...except
    #错误一定会发生,但是无法预知错误发生条件
    #CS结构的软件能够用到,客户的操作无法控制,客户单方面把软件终止掉,
    #因为服务端客户端都是基于TCP链接的,但客户端单方面把链接断开,服务端也会受影响
    #这种错误有可能发生,服务端不能受影响,这就用到了异常处理

    class EgonException(BaseException):
        def __init__(self,msg):
            self.msg=msg
        def __str__(self):
            return '<%s>' % self.msg
    
    raise EgonException('egon的异常')

    基于tcp协议的socket实现简单通信

    简单的套接字

    1.服务端

    # socket.AF_INET 用什么类型的套接字,地址家族
    # socket.SOCK_STREAM 流式套接字,就是调用tcp

    import socket
    #买手机
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 基于tcp的套接字
    
    #插卡
    phone.bind(('127.0.0.1',8080))
    
    #开机
    phone.listen(5) #最大挂起的链接数
    
    #等电话链接
    print('server start...')
    conn,client_addr=phone.accept() # (建立好的tcp链接,客户端的ip和端口)
    print('链接',conn)
    print(client_addr)

    #基于建立的链接,收发消息
    client_data=conn.recv(1024)
    print('客户端的消息',client_data)
    conn.send(client_data.upper())

    #挂电话
    conn.close()

    #关机
    phone.close()

    2.客户端

    import socket
    
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    phone.connect(('127.0.0.1',8080))
    
    phone.send('hello'.encode('utf-8')) # 需要二进制类型,转为bytes类型
    server_data=phone.recv(1024) #服务端回复的消息
    print('服务端回应的消息',server_data)
    
    phone.close()

    加上通信循环

    #服务端

    import socket
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 
    phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 用来解决Address already in use 问题
    
    phone.bind(('127.0.0.1',8080))
    
    phone.listen(5)
    
    print('server start...')
    conn,client_addr=phone.accept()
    
    #基于建立的链接,收发消息
    while True: #通讯循环
        client_data=conn.recv(1024)
        conn.send(client_data.upper())
    
    conn.close()
    phone.close()

    #客户端

    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')) 
        server_data=phone.recv(1024) 
        print(server_data.decode('utf-8'))
    
    phone.close()

    加上链接循环的套接字

    服务端

    import socket
    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))
    
    phone.listen(5)
    
    print('server start...')
    
    #链接循环
    while True: # 这个循环是用来解决当有Exception异常时,是客户端断了,把这个断掉的资源回收掉
    # 重新等待其他链接链接进来
    
        conn,client_addr=phone.accept()
    
        while True:
    #此处用try..except是由于和多个客户端建立了链接,一个客户端断掉链接后会服务端抛出异常,
    #这样就导致其余的客户端也会发生错误,抛异常,为了使服务端能够继续和没有主动断掉链接的客户端
    #继续通信,不崩溃,需要用到这个try..except..,但是只能解决不抛出异常的问题,不能解决让其他客户端
    #进来和服务端建立通信
            try: # 用于解决windows方面的问题 
                client_data=conn.recv(1024)
                if not client_data:break #主要用于linux系统和mac,客户端单方面断掉后,recv不会阻塞掉,
    #而是不断的收空,一直send,对方没有收,一直死循环这个try内的
    #代码,所以需要加上 if not client_data:break ,如果收到的消息
    #为空,肯定是客户端断掉了链接
                conn.send(client_data.upper())
            except Exception:
                break # 把这个通信循环直接断掉
    
    conn.close()
    
    phone.close()

    #客户端1

    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')) 
        server_data=phone.recv(1024) 
        print(server_data.decode('utf-8'))
    
    phone.close()

    #客户端2

    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')) 
        server_data=phone.recv(1024) 
        print(server_data.decode('utf-8'))
    
    phone.close()

    #客户端3

    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')) 
        server_data=phone.recv(1024) 
        print(server_data.decode('utf-8'))
    
    phone.close()

    模拟ssh远程执行命令

    #客户端

    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')) #linux系统的话改为utf-8
    
    phone.close()

    #服务端

    import socket
    import subprocess
    
    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))
    phone.listen(5) 
    print('server start...')
    while True:
        conn,client_addr=phone.accept()
    
            while True: 
                try:
                    cmd=conn.recv(1024) #bytes格式
                    if not cmd:break
    #执行命令,拿到结果(subprocess命令的执行结果是bytes类型)
                    res=subprocess.Popen(cmd.decode('utf-8'), # 需要字符串格式,
                                         shell=True,
                                         stdout=subprocess.PIPE,
                                         stderr=subprocess.PIPE)
    
                    stdout=res.stdout.read() #从正确的管道里面读,但是gbk编码
                    stderr=res.stderr.read() #从错误管道里面读,但是gbk编码
    
                    conn.send(stdout+stderr)
                except Exception:
                    break
                conn.close()
    
    phone.close()

    解决粘包问题

    只有TCP有粘包现象,UDP永远不会粘包
    由于TCP是流式的,没有开头没有结尾,就会发生粘包现象
    UDP是数据报协议,它不是基于流的

    粘包现象

    服务端

    from socket import *
    s=socket(AF_INET,SOCK_STREAM)
    s.bind(('127.0.0.1',8080))
    s.listen(5)
    
    conn,addr=s.accept()
    
    #收发消息
    data1=conn.recv(1024)
    print('data1',data1)
    
    data2=conn.recv(1024)
    print('data2',data2)
    
    conn.close()
    s.close()
    
    #data1: b'helloworld'
    #data2 b''
    上面的结果不一定会发生,取决于算法要不要合并到一起发送过去,
    多尝试几次,就会发生

    客户端

    from socket import *
    c=socket(AF_INET,SOCK_STREAM)
    c.connect(('127.0.0.1',8080))
    
    c.send('hello'.encode('utf-8'))
    c.send('world'.encode('utf-8'))
    
    c.close()

    ssh远程执行命令+定制报头

    客户端

    import socket
    import struct
    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'))
    
        #先收报头
        header=phone.recv(4)
        total_size=struct.unpack('i',header)[0]
    
        #再收命令的执行结果
        recv_size=0
        data=b'' #结果
        while recv_size < total_size:
            recv_data=phone.recv(1024)
            recv_size+=len(recv_data)
            data+=recv_data
            print(data.decode('gbk'))
    
    phone.close()

    服务端

    import socket
    import struct
    import subprocess
    
    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))
    phone.listen(5) 
    print('server start...') 
    while True:
        conn,client_addr=phone.accept()
        print(conn,client_addr)
        while True: 
            try:
                cmd=conn.recv(1024)
                if not cmd:break
    
                res=subprocess.Popen(cmd.decode('utf-8'), 
                                     shell=True,
                                     stdout=subprocess.PIPE,
                                     stderr=subprocess.PIPE)
    
                stdout=res.stdout.read() 
                stderr=res.stderr.read() 
    
                #制作报头 
                header=struct.pack('i',len(stdout)+len(stderr))
                #先发报头(固定长度,这里一直是4)
                conn.send(header)
    
                #再发真实数据
                conn.send(stdout)
                conn.send(stderr)
    
            except Exception:
                break
            conn.close()
    
    phone.close()
    
    #此方法的问题是最大只能发小于2G的内容

    定制报头的正确方式

    客户端

    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'))
    
        #先收报头的长度
        struct_res=phone.recv(4)
        header_size=struct.unpack('i',struct_res)[0]
    
        #再收报头
        header_bytes=phone.recv(header_size)
        head_json=header_bytes.decode('utf-8')
        head_dic=json.loads(head_json)
    
        total_size=head_dic['total_size']
        #再收命令的执行结果
        recv_size=0
        data=b'' #结果
        while recv_size < total_size:
            recv_data=phone.recv(1024)
            recv_size+=len(recv_data)
            data+=recv_data
    
            print(data.decode('gbk'))
    
    phone.close()

    服务端

    import socket
    import struct
    import subprocess
    import json
    
    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))
    phone.listen(5) 
    print('server start...') 
    while True:
        conn,client_addr=phone.accept()
        print(conn,client_addr)
        while True: 
            try:
                cmd=conn.recv(1024)
                if not cmd:break
    
                res=subprocess.Popen(cmd.decode('utf-8'), 
                                     shell=True,
                                     stdout=subprocess.PIPE,
                                     stderr=subprocess.PIPE)
    
                                     stdout=res.stdout.read() 
                                     stderr=res.stderr.read() 
                #制作报头 
                header_dic={'total_size':len(stdout)+len(stderr),'md5':None}
                header_json=json.dumps(header_dic)
                header_bytes=header_json.encode('utf-8')
    
                #先发报头的长度(固定4个bytes)
                conn.send(struct.pack('i',len(header_bytes)))
    
                #再发报头
                conn.send(header_bytes)
    
                #再发真实数据
                conn.send(stdout)
                conn.send(stderr)
    
            except Exception:
                break
            conn.close()
    
    phone.close()

    socketserver实现并发

    服务端

    import socketserver
    
    class MyTcphandler(socketserver.BaseRequestHandler):
        def handle(self):
            while True: #通信循环
                data=self.request.recv(1024) # 这个self.request就是conn
                self.request.send(data.upper())
    
    if __name__ == '__main__':
    #取代链接循环
    server=socketserver.ThreadingTCPServer(('127.0.0.1',8080),MyTcphandler)
    server.serve_forever()

    客户端1

    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'))
        server_data=phone.recv(1024)
        print(server_data.decode('utf-8'))
    
    phone.close()

    客户端2

    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'))
    server_data=phone.recv(1024)
    print(server_data.decode('utf-8'))
    
    phone.close()

    客户端3

    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'))
        server_data=phone.recv(1024)
        print(server_data.decode('utf-8'))
    
    phone.close()

    基于UDP的套接字

    UDP是无连接的,UDP发送的数据本身就自带报头

    udp服务端

    s=socket()
    s.bind()   
    #这一步tcp需要listen,listen()里面需要指定挂起的连接数,而udp没有连接,
    #所以没有listen
    
    #对于tcp来讲,这一步应该是链接循环,但是udp没有
    
    #udp的通信循环
    inf_loop:
        cs=s.recvfrom()/s.sendto()
     s.close()

    udp客户端

    c=socket()
    comm_loop:  #通信循环
        c.sendto()/cs.recvfrom()
    c.close()

    完整实例

     1 udp服务端
     2 
     3 from socket import *
     4 
     5 udp_server=socket(AF_INET, SOCK_DGRAM)
     6 udp_server.bind(('127.0.0.1',8080))
     7 
     8 while True:
     9     data, client_addr=udp_server.recvfrom(1024)
    10     udp_server.sendto(data.upper(),client_addr)
    11 
    12 udp客户端
    13 
    14 from socket import *
    15 
    16 udp_client=socket(AF_INET, SOCK_DGRAM)
    17 
    18 while True:
    19     msg=input('>>: ').strip()
    20     udp_client.sendto(msg.encode('utf-8'),('127.0.0.1',8080))
    21     data,server_addr=udp_client.recvfrom(1024)
    22     print(data.decode('utf-8'))
    View Code

    基于socketserver实现并发的udp套接字

    udp服务端

    import socketserver
    class MyUDPhandler(socketserver.BaseRequestsHandler):
        def handle(self):
            print(self.request) 
            #(b'hello',<socket.socket fd=200, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0, laddr=('127.0.0.1',8080>)
            #(客户端发来的消息,用这个可以给客户端回消息)
            self.request[1].sendto(self.request[0].upper(),self.client_addr)
            #self.request[1] 套接字
            #self.client_addr 要回复的客户端地址
    
    if __name__ == '__main__':
        s=socketserver.ThreadingUDPServer(('127.0.0.1',8080),MyUDPhandler)
        s.serve_forever()

    udp客户端

    from socket import *
    
    udp_client=socket(AF_INET, SOCK_DGRAM)
    
    while True:
        msg=input('>>: ').strip()
        udp_client.sendto(msg.encode('utf-8'),('127.0.0.1',8080))
        data,server_addr=udp_client.recvfrom(1024)
        print(data.decode('utf-8'))
  • 相关阅读:
    linux下编译sphinx拓展
    Java为什么使用连接池
    内部类之.this&&.new
    何为代理
    Qt install Phonon
    Gcc简介与常用命令
    Vim的设置和使用——编程者
    QT程序启动界面的使用
    slide from one widget to another
    Hide the common top menu in Ubuntu 12.04
  • 原文地址:https://www.cnblogs.com/Ryans-World/p/7405362.html
Copyright © 2020-2023  润新知