Python 實現 WebSocket 通信的過程詳解
WebSocket是一種在Web應用程序中實現雙向通信的協(xié)議。與傳統(tǒng)的HTTP請求-響應模型不同,WebSocket允許服務器主動向客戶端推送數據,實現實時性和互動性。
以下是WebSocket的一些關鍵特點:
雙向通信:WebSocket提供了全雙工通信,允許服務器和客戶端同時發(fā)送和接收數據。相比起傳統(tǒng)的HTTP請求-響應模型,WebSocket使得服務器可以主動推送數據給客戶端,而不需要客戶端首先發(fā)起請求。
持久連接:與HTTP請求不同,WebSocket連接是持久的,即一旦建立連接,它將保持打開狀態(tài),直到其中一方關閉連接或發(fā)生錯誤。
輕量級協(xié)議:WebSocket使用簡單的消息傳遞協(xié)議,在傳輸數據時減少了額外的開銷。它采用二進制幀格式或文本幀格式來發(fā)送數據。
跨域支持:WebSocket支持跨域通信,允許在不同域名或端口之間建立連接和交換數據。
WebSocket在許多場景下非常有用,特別是實時通信和實時數據更新方面。它被廣泛應用于在線聊天、實時游戲、股票市場行情、協(xié)同編輯和實時協(xié)作等應用程序中。
在編程中,可以使用WebSocket API來建立WebSocket連接,并使用WebSocket的事件和方法來處理連接的打開、關閉、錯誤和消息等操作。常見的編程語言和框架提供了對WebSocket的支持,使得開發(fā)者可以方便地實現WebSocket功能。
實現簡單通信
這段代碼實現了一個簡單的WebSocket客戶端,通過與服務器建立連接,可以實現實時的雙向通信。需要注意的是,連接的URL和端口需要根據實際情況進行修改,確保與服務器端的WebSocket服務端口一致。
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <script src="https://cdn.lyshark.com/jquery/3.5.1/jquery.min.js"></script> </head> <body> <ul id="content"></ul> <form class="form"> <input type="text" placeholder="請輸入發(fā)送的消息" class="message" id="message"/> <input type="button" value="連接" id="connect" class="connect"/> <input type="button" value="發(fā)送" id="send" class="connect"/> </form> <script type="text/javascript"> var oUl=document.getElementById('content'); var oConnect=document.getElementById('connect'); var oSend=document.getElementById('send'); var websocket=null; oConnect.onclick=function(){ websocket=new WebSocket('ws://127.0.0.1:10083'); <!--客戶端鏈接后觸發(fā)--> websocket.onopen=function(){ oUl.innerHTML+="<li>客戶端已連接</li>"; } <!--收到消息后觸發(fā)--> websocket.onmessage=function(evt){ oUl.innerHTML+="<li>"+evt.data+"</li>"; } <!--關閉后觸發(fā)--> websocket.onclose=function(){ oUl.innerHTML+="<li>客戶端已斷開連接</li>"; }; <!--出錯后觸發(fā)--> websocket.onerror=function(evt){ oUl.innerHTML+="<li>"+evt.data+"</li>"; }; }; oSend.onclick=function(){ if(websocket){ websocket.send($("#message").val()) } } </script> </body> </html>
后端的main.py執(zhí)行處理任務,主要處理流程集中在handler_msg函數上,如下這段代碼實現了一個簡單的WebSocket服務器。它使用了Python的socket和threading模塊來創(chuàng)建服務器,并處理與客戶端的握手和數據交互。
代碼中的主要函數和功能包括:
get_headers(data)
: 從請求數據中獲取請求頭部信息,并將其轉換為字典格式。
parse_payload(payload)
: 對接收到的數據進行解碼,還原出原始消息。
send_msg(conn, msg_bytes)
: 封裝并發(fā)送數據到瀏覽器。
recv_msg(conn)
: 從瀏覽器中接收數據,并解析出原始消息。
handler_accept(sock)
: 建立握手流程,處理與客戶端的連接和握手過程。在握手完成后,創(chuàng)建一個新的線程來處理與客戶端的數據交互。
handler_msg(connect)
: 處理與客戶端的數據交互。通過循環(huán)不斷接收客戶端發(fā)送的消息,并發(fā)送一個固定的回復消息。
主函數部分:創(chuàng)建一個socket對象并綁定到本地主機的端口10083上,然后開始監(jiān)聽客戶端的連接請求。當有新的連接請求時,將其分配給handler_accept()
函數進行處理,并在新線程中處理與客戶端的數據交互。
該代碼實現了一個簡單的WebSocket服務器,可以接收來自客戶端的連接請求,并與客戶端進行數據交互。需要注意的是,服務器綁定的IP地址和端口號可以根據實際需求進行修改。
import socket,struct,hashlib,base64 import threading # 獲取請求頭部數據,并將請求頭轉換為字典 def get_headers(data): headers = {} data = str(data, encoding="utf-8") header, body = data.split("\r\n\r\n", 1) header_list = header.split("\r\n") for i in header_list: i_list = i.split(":", 1) if len(i_list) >= 2: headers[i_list[0]] = "".join(i_list[1::]).strip() else: i_list = i.split(" ", 1) if i_list and len(i_list) == 2: headers["method"] = i_list[0] headers["protocol"] = i_list[1] print("請求類型: {} 請求協(xié)議: {}".format(i_list[0],i_list[1])) return headers # 接收數據時的解碼過程 def parse_payload(payload): payload_len = payload[1] & 127 if payload_len == 126: mask = payload[4:8] decoded = payload[8:] elif payload_len == 127: mask = payload[10:14] decoded = payload[14:] else: mask = payload[2:6] decoded = payload[6:] # 將所有數據全部收集起來,對所有字符串編碼 bytes_list = bytearray() for i in range(len(decoded)): chunk = decoded[i] ^ mask[i % 4] bytes_list.append(chunk) body = str(bytes_list, encoding='utf-8') return body # 封裝并發(fā)送數據到瀏覽器 def send_msg(conn, msg_bytes): # 接收的第一個字節(jié)都是x81不變 first_byte = b"\x81" length = len(msg_bytes) if length < 126: first_byte += struct.pack("B", length) elif length <= 0xFFFF: first_byte += struct.pack("!BH", 126, length) else: first_byte += struct.pack("!BQ", 127, length) msg = first_byte + msg_bytes conn.sendall(msg) return True # 從瀏覽器中接收數據 def recv_msg(conn): data_recv = conn.recv(8096) if data_recv[0:1] == b"\x81": data_parse = parse_payload(data_recv) return data_parse return False # 建立握手流程并創(chuàng)建 handler_msg 完成數據收發(fā) def handler_accept(sock): while True: conn, addr = sock.accept() data = conn.recv(8096) headers = get_headers(data) # 對請求頭中的sec-websocket-key進行加密 response_tpl = "HTTP/1.1 101 Switching Protocols\r\n" \ "Upgrade:websocket\r\n" \ "Connection: Upgrade\r\n" \ "Sec-WebSocket-Accept: %s\r\n" \ "WebSocket-Location: ws://%s\r\n\r\n" # 加鹽操作,此處是H5規(guī)范定義好的 magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' if headers.get('Sec-WebSocket-Key'): value = headers['Sec-WebSocket-Key'] + magic_string # 對數據進行加解密 ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest()) response_str = response_tpl % (ac.decode('utf-8'), headers.get("Host")) # 相應握手包數據 conn.sendall(bytes(response_str, encoding="utf-8")) t = threading.Thread(target=handler_msg, args=(conn, )) t.start() # 主函數,用于實現數據交互 def handler_msg(connect): with connect as connect_ptr: while True: try: recv = recv_msg(connect_ptr) print("接收數據: {}".format(recv)) send_msg(connect_ptr, bytes("hello lyshark", encoding="utf-8")) except Exception: exit(0) if __name__ == "__main__": sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(("127.0.0.1", 10083)) sock.listen(5) t = threading.Thread(target=handler_accept(sock)) t.start()
繪制動態(tài)圖形
如下這段代碼實現了一個簡單的實時CPU數據展示頁面。通過WebSocket與服務器建立連接,并實時接收和展示來自服務器的CPU數據。需要注意的是,頁面中WebSocket連接的IP地址和端口號需要根據實際情況進行修改,確保與服務器建立正確的連接。另外,使用ECharts庫來繪制折線圖需要保證頁面能夠正確加載ECharts的JavaScript文件。
代碼中的主要部分包括:
引入ECharts庫:使用<script>
標簽引入ECharts庫的JavaScript文件,以便在頁面中使用ECharts。
HTML結構:在頁面中創(chuàng)建一個<div>
元素,用于顯示折線圖。
JavaScript代碼:定義了一個名為display
的函數,用于初始化和更新折線圖。該函數接收兩個參數:時間數組和CPU數據數組。通過調用ECharts的setOption
方法,配置折線圖的樣式和數據,并將其顯示在指定的<div>
元素中。
WebSocket通信:創(chuàng)建一個WebSocket對象,與服務器建立WebSocket連接。通過監(jiān)聽onmessage
事件,接收服務器發(fā)送的CPU數據。將接收到的數據解析為JSON格式,將時間和CPU數據分別添加到對應的數組中。當時間數組和CPU數據數組的長度達到10時,刪除數組中最舊的數據,并調用display
函數更新折線圖。
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <script type="text/javascript" src="https://cdn.lyshark.com/echarts/5.0.0/echarts.min.js"></script> </head> <body> <center><div id="main" style="height:400px;width:90%;border:1px solid #eecc11;padding:10px;"></div></center> <script type="text/javascript" charset="UTF-8"> var display = function(time,cpu) { var main = echarts.init(document.getElementById(("main"))); var option = { xAxis: { type: 'category', data: time }, yAxis: { type: 'value' }, series: [{ type: 'line', smooth:0.3, symbol: 'none', color: 'blue', smooth: true, areaStyle: { color: '#0000CD', origin: 'start', opacity: 0.5 }, data: cpu }] }; main.setOption(option,true); }; </script> <script type="text/javascript" charset="UTF-8"> var ws=new WebSocket('ws://127.0.0.1:10083'); var time =["","","","","","","","","",""]; var cpu = [0,0,0,0,0,0,0,0,0,0]; ws.onmessage=function(evt) { var recv = JSON.parse(evt.data); time.push(recv.response[0]); cpu.push(parseFloat(recv.response[1])); if(time.length >=10){ time.shift(); cpu.shift(); console.log("時間:" + time + " --> CPU數據: " + cpu); display(time,cpu) } } </script> </body> </html>
后臺部分我們主要代碼不需要動,只需要修改handler_msg
處理流程即可,代碼能夠接受客戶端的連接請求,并定時發(fā)送CPU數據給客戶端。需要注意的是,代碼中的sock.bind(("127.0.0.1", 10083))
綁定的是本地地址和指定的端口號,需要根據實際情況進行修改。另外,需要確保在服務器環(huán)境中安裝了psutil
庫,以便獲取CPU使用率數據。
import socket,struct,hashlib,base64 import threading # 獲取請求頭部數據,并將請求頭轉換為字典 def get_headers(data): headers = {} data = str(data, encoding="utf-8") header, body = data.split("\r\n\r\n", 1) header_list = header.split("\r\n") for i in header_list: i_list = i.split(":", 1) if len(i_list) >= 2: headers[i_list[0]] = "".join(i_list[1::]).strip() else: i_list = i.split(" ", 1) if i_list and len(i_list) == 2: headers["method"] = i_list[0] headers["protocol"] = i_list[1] print("請求類型: {} 請求協(xié)議: {}".format(i_list[0],i_list[1])) return headers # 接收數據時的解碼過程 def parse_payload(payload): payload_len = payload[1] & 127 if payload_len == 126: mask = payload[4:8] decoded = payload[8:] elif payload_len == 127: mask = payload[10:14] decoded = payload[14:] else: mask = payload[2:6] decoded = payload[6:] # 將所有數據全部收集起來,對所有字符串編碼 bytes_list = bytearray() for i in range(len(decoded)): chunk = decoded[i] ^ mask[i % 4] bytes_list.append(chunk) body = str(bytes_list, encoding='utf-8') return body # 封裝并發(fā)送數據到瀏覽器 def send_msg(conn, msg_bytes): # 接收的第一個字節(jié)都是x81不變 first_byte = b"\x81" length = len(msg_bytes) if length < 126: first_byte += struct.pack("B", length) elif length <= 0xFFFF: first_byte += struct.pack("!BH", 126, length) else: first_byte += struct.pack("!BQ", 127, length) msg = first_byte + msg_bytes conn.sendall(msg) return True # 從瀏覽器中接收數據 def recv_msg(conn): data_recv = conn.recv(8096) if data_recv[0:1] == b"\x81": data_parse = parse_payload(data_recv) return data_parse return False # 建立握手流程并創(chuàng)建 handler_msg 完成數據收發(fā) def handler_accept(sock): while True: conn, addr = sock.accept() data = conn.recv(8096) headers = get_headers(data) # 對請求頭中的sec-websocket-key進行加密 response_tpl = "HTTP/1.1 101 Switching Protocols\r\n" \ "Upgrade:websocket\r\n" \ "Connection: Upgrade\r\n" \ "Sec-WebSocket-Accept: %s\r\n" \ "WebSocket-Location: ws://%s\r\n\r\n" # 加鹽操作,此處是H5規(guī)范定義好的 magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' if headers.get('Sec-WebSocket-Key'): value = headers['Sec-WebSocket-Key'] + magic_string # 對數據進行加解密 ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest()) response_str = response_tpl % (ac.decode('utf-8'), headers.get("Host")) # 相應握手包數據 conn.sendall(bytes(response_str, encoding="utf-8")) t = threading.Thread(target=handler_msg, args=(conn, )) t.start() # 主函數,用于實現數據交互 def handler_msg(conn): with conn as c: while True: try: times = time.strftime("%M:%S", time.localtime()) data = psutil.cpu_percent(interval=None, percpu=True) print("處理時間: {} --> 處理負載: {}".format(times, data)) send_msg(c, bytes(json.dumps({"response": [times, data]}), encoding="utf-8")) time.sleep(60) except Exception: exit(0) if __name__ == "__main__": sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(("127.0.0.1", 10083)) sock.listen(5) t = threading.Thread(target=handler_accept(sock)) t.start()
實現動態(tài)交互
新建index.html寫入如下代碼,這段代碼實現了一個Web頁面,包含了一個可交互的終端,可以通過輸入地址范圍和執(zhí)行命令來批量執(zhí)行命令,并實時顯示執(zhí)行結果。需要注意的是,代碼中的WebSocket連接地址和端口號需要根據實際情況進行修改,確保與后端WebSocket服務器的地址和端口號一致。
代碼中的主要部分包括:
引入CSS和JavaScript庫:頁面引入了xterm.css
和xterm.js
來加載和渲染終端的外觀樣式和功能,同時還引入了jQuery庫用于簡化DOM操作。
創(chuàng)建終端實例:通過new Terminal()
創(chuàng)建了一個終端實例term
,設置了終端的行數、列數、換行符轉換、光標閃爍等屬性。
WebSocket連接:通過new WebSocket()
創(chuàng)建了一個WebSocket實例sock
,并指定了與后端的WebSocket服務器的地址和端口號。
終端打開和消息接收:在sock.onopen
回調函數中,調用term.open()
打開終端,并在控制臺輸出WebSocket連接成功的消息。在sock.onmessage
回調函數中,解析接收到的JSON數據,提取地址和狀態(tài)信息,并將其顯示在終端中。
執(zhí)行命令:通過$('#send_message').click()
事件處理函數,獲取輸入框中的地址和命令信息,將其封裝成JSON格式,并通過WebSocket發(fā)送給后端。
<html> <head> <meta charSet="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <link rel="stylesheet" rel="external nofollow" /> <script src="https://cdn.lyshark.com/xterm/xterm.js"></script> <script src="https://cdn.lyshark.com/jquery/3.5.1/jquery.min.js" type="text/javascript"></script> </head> <body> <div id="terminal"></div> <input type="text" id="address" placeholder="主機范圍 127.0.0.1-100" style="width:200px;height:40px"/> <input type="text" id="command" placeholder="執(zhí)行命令 ls -lh " style="width:400px;height:40px"/> <input type="button" id="send_message" value="批量執(zhí)行"> <!--實現格式化字符串--> <script type="text/javascript"> $.format = function(source, params) { if (arguments.length == 1) return function() { var args = $.makeArray(arguments); args.unshift(source); return $.format.apply(this, args); }; if (arguments.length > 2 && params.constructor != Array) { params = $.makeArray(arguments).slice(1); } if (params.constructor != Array) { params = [params]; } $.each(params, function(i, n) { source = source.replace(new RegExp("\\{" + i + "\\}", "g"), n); }); return source; }; </script> <!--打開終端,并開始執(zhí)行命令--> <script type="text/javascript"> $(function(){ var window_width = $(window).width()-200; var window_height = $(window).height()-300; var term = new Terminal( { cols: Math.floor(window_width/9), rows: Math.floor(window_height/20), convertEol: true, cursorBlink:false, }); var sock = new WebSocket("ws://127.0.0.1:10083"); sock.onopen = function () { term.open(document.getElementById('terminal')); console.log('WebSocket Open'); }; sock.onmessage = function (recv) { var data = JSON.parse(recv.data); console.log(data['addr'] + ' -- ' + data['status']); var temp = "\x1B[1;3;35m 地址:[ {0} ] \x1B[0m --> \x1B[1;3;33m 狀態(tài):[ {1} ] \x1B[0m"; var string = $.format(temp, data['addr'],data['status']); term.writeln(string); }; $('#send_message').click(function () { var message ={"address":null,"command":null}; message['address'] = $("#address").val(); message['command'] = $("#command").val(); var send_data = JSON.stringify(message); window.sock.send(send_data); }); window.sock = sock; }); </script>
后端代碼如下所示,其中核心代碼handler_msg(conn),用于處理接收到的消息,解析地址范圍,并針對每個地址執(zhí)行命令。執(zhí)行結果會通過WebSocket發(fā)送回客戶端,并在后臺打印出對應的主機和執(zhí)行的命令。需要注意的是,代碼中的函數recv_msg()
和send_msg()
沒有提供具體的實現,你需要根據具體的需求自行實現這兩個函數。
CalculationIP(Addr_Count)
函數用于解析地址范圍,并生成對應的IP地址列表。參數Addr_Count
是一個地址范圍字符串,如"127.0.0.1-100"
。函數首先根據"-"符號將地址范圍字符串分割為起始IP和結束IP的兩部分。然后根據起始IP生成IP地址的前三個部分,即IP地址的頭部。接著根據起始IP和結束IP的范圍,循環(huán)生成完整的IP地址,并將其添加到結果列表中。最后返回生成的IP地址列表。
handler_msg(conn)
函數是一個無限循環(huán),用于處理消息并執(zhí)行命令。在循環(huán)中,首先使用eval(recv_msg(c))
函數接收并解析JSON格式的消息數據。然后從消息中獲取地址和命令信息。接下來調用CalculationIP()
函數生成地址列表。然后遍歷地址列表,對每個地址執(zhí)行命令,并將執(zhí)行結果發(fā)送回客戶端。執(zhí)行過程中,會打印出對應的主機和執(zhí)行的命令。在每次執(zhí)行完命令后,程序會暫停1秒鐘。如果在執(zhí)行過程中發(fā)生異常,程序會退出。
import socket,struct,hashlib,base64 import threading,psutil # 獲取請求頭部數據,并將請求頭轉換為字典 def get_headers(data): headers = {} data = str(data, encoding="utf-8") header, body = data.split("\r\n\r\n", 1) header_list = header.split("\r\n") for i in header_list: i_list = i.split(":", 1) if len(i_list) >= 2: headers[i_list[0]] = "".join(i_list[1::]).strip() else: i_list = i.split(" ", 1) if i_list and len(i_list) == 2: headers["method"] = i_list[0] headers["protocol"] = i_list[1] print("請求類型: {} 請求協(xié)議: {}".format(i_list[0],i_list[1])) return headers # 接收數據時的解碼過程 def parse_payload(payload): payload_len = payload[1] & 127 if payload_len == 126: mask = payload[4:8] decoded = payload[8:] elif payload_len == 127: mask = payload[10:14] decoded = payload[14:] else: mask = payload[2:6] decoded = payload[6:] # 將所有數據全部收集起來,對所有字符串編碼 bytes_list = bytearray() for i in range(len(decoded)): chunk = decoded[i] ^ mask[i % 4] bytes_list.append(chunk) body = str(bytes_list, encoding='utf-8') return body # 封裝并發(fā)送數據到瀏覽器 def send_msg(conn, msg_bytes): # 接收的第一個字節(jié)都是x81不變 first_byte = b"\x81" length = len(msg_bytes) if length < 126: first_byte += struct.pack("B", length) elif length <= 0xFFFF: first_byte += struct.pack("!BH", 126, length) else: first_byte += struct.pack("!BQ", 127, length) msg = first_byte + msg_bytes conn.sendall(msg) return True # 從瀏覽器中接收數據 def recv_msg(conn): data_recv = conn.recv(8096) if data_recv[0:1] == b"\x81": data_parse = parse_payload(data_recv) return data_parse return False # 建立握手流程并創(chuàng)建 handler_msg 完成數據收發(fā) def handler_accept(sock): while True: conn, addr = sock.accept() data = conn.recv(8096) headers = get_headers(data) # 對請求頭中的sec-websocket-key進行加密 response_tpl = "HTTP/1.1 101 Switching Protocols\r\n" \ "Upgrade:websocket\r\n" \ "Connection: Upgrade\r\n" \ "Sec-WebSocket-Accept: %s\r\n" \ "WebSocket-Location: ws://%s\r\n\r\n" # 加鹽操作,此處是H5規(guī)范定義好的 magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' if headers.get('Sec-WebSocket-Key'): value = headers['Sec-WebSocket-Key'] + magic_string # 對數據進行加解密 ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest()) response_str = response_tpl % (ac.decode('utf-8'), headers.get("Host")) # 相應握手包數據 conn.sendall(bytes(response_str, encoding="utf-8")) t = threading.Thread(target=handler_msg, args=(conn, )) t.start() def CalculationIP(Addr_Count): ret = [] try: IP_Start = str(Addr_Count.split("-")[0]).split(".") IP_Heads = str(IP_Start[0] + "." + IP_Start[1] + "." + IP_Start[2] +".") IP_Start_Range = int(Addr_Count.split(".")[3].split("-")[0]) IP_End_Range = int(Addr_Count.split("-")[1]) for item in range(IP_Start_Range,IP_End_Range+1): ret.append(IP_Heads+str(item)) return ret except Exception: return 0 def handler_msg(conn): with conn as c: while True: try: ref_json = eval(recv_msg(c)) address = ref_json.get("address") command = ref_json.get("command") address_list = CalculationIP(address) for ip in address_list: response = {'addr': ip, 'status': 'success'} print("對主機: {} --> 執(zhí)行: {}".format(ip,command)) send_msg(c, bytes(json.dumps(response) , encoding="utf-8")) time.sleep(1) except Exception: exit(0) if __name__ == "__main__": sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(("127.0.0.1", 10083)) sock.listen(5) t = threading.Thread(target=handler_accept(sock)) t.start()
到此這篇關于Python 實現 WebSocket 通信的文章就介紹到這了,更多相關Python WebSocket 通信內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Python實現批量把SVG格式轉成png、pdf格式的代碼分享
這篇文章主要介紹了Python實現批量把SVG格式轉成png、pdf格式的代碼分享,本文代碼需要引用一個第三方模塊cairosvg,需要的朋友可以參考下2014-08-08Python中淺拷貝copy與深拷貝deepcopy的簡單理解
今天小編就為大家分享一篇關于Python中淺拷貝copy與深拷貝deepcopy的簡單理解,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2018-10-10