1. 程式人生 > >Python自動化開發課堂筆記【Day08】 - Python進階(面向對象的高級用法,網絡編程)

Python自動化開發課堂筆記【Day08】 - Python進階(面向對象的高級用法,網絡編程)

sta 自然 log 報錯 面向 read urn total 析構函數

面向對象的高級用法

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()

Python自動化開發課堂筆記【Day08】 - Python進階(面向對象的高級用法,網絡編程)