一 多線程
1.1 進程和線程
- 進程: 就是一個程序,運行在系統(tǒng)之上,稱這個程序為一個運行進程,并分配進程ID方便系統(tǒng)管理。
- 線程:線程是歸屬于進程的,一個進程可以開啟多個線程,執(zhí)行不同的工作,是進程的實際工作最小單位。
- 操作系統(tǒng)中可以運行多個進程,即多任務運行
- 一個進程內可以運行多個線程,即多線程運行
- 進程之間是內存隔離的, 即不同的進程擁有各自的內存空間。
- 線程之間是內存共享的,線程是屬于進程的,一個進程內的多個線程之間是共享這個進程所擁有的內存空間的。
1.2 并行、并發(fā)執(zhí)行概念
-
在Python中,多線程用于實現(xiàn)并行和并發(fā)執(zhí)行任務。雖然多線程可以讓你同時執(zhí)行多個任務,但由于Python的全局解釋鎖(Global Interpreter Lock,GIL)的存在,多線程并不能實現(xiàn)真正的多核并行。然而,多線程仍然可以用于執(zhí)行I/O密集型任務,因為在這些任務中,線程可能會在等待I/O操作完成時釋放GIL,從而允許其他線程運行。
-
并行執(zhí)行:
- 并行執(zhí)行是指多個任務在同一時刻同時運行,各自獨立地占用一個CPU核心。
- 在Python中,由于GIL的存在,多線程并不適合用于CPU密集型任務的并行執(zhí)行。
- 多個進程同時在運行,即不同的程序同時運行,稱之為:多任務并行執(zhí)行
- 一個進程內的多個線程同時在運行,稱之為:多線程并行執(zhí)行
-
并發(fā)執(zhí)行:
- 并發(fā)執(zhí)行是指多個任務交替執(zhí)行,通過快速切換執(zhí)行任務的上下文來實現(xiàn)“同時”執(zhí)行的錯覺。
- 這在處理I/O密集型任務時非常有效,因為線程可能會在等待I/O完成時讓出CPU資源給其他線程。
1.3 多線程編程
-
在Python中,可以使用
threading
模塊來創(chuàng)建和管理多線程。 -
threading.Thread
類可以創(chuàng)建一個線程對象,用于執(zhí)行特定的任務函數(shù)。 -
threading.Thread
類的一般語法和一些常用參數(shù):thread_obj = threading.Thread(target=function_name, args=(), kwargs={}, daemon=False) # 啟動線程 thread_obj.start()
-
target
: 必需的參數(shù),用于指定線程要執(zhí)行的函數(shù)(任務)。函數(shù)會在新線程中運行。 -
args
: 可選參數(shù),用于傳遞給目標函數(shù)的位置參數(shù),以元組形式提供。如果函數(shù)不需要參數(shù),可以傳遞一個空元組或省略這個參數(shù)。 -
kwargs
: 可選參數(shù),用于傳遞給目標函數(shù)的關鍵字參數(shù),以字典形式提供。如果函數(shù)不需要關鍵字參數(shù),可以傳遞一個空字典或省略這個參數(shù)。 -
daemon
: 可選參數(shù),布爾值,用于指定線程是否為守護線程。守護線程會在主線程結束時被終止,而非守護線程會等待所有線程完成后再終止。 -
使用
threading.Thread
類創(chuàng)建線程對象并啟動線程
import threading
import time
def print_numbers():
for i in range(1, 6):
print(f"Number: {i}")
time.sleep(1)
def print_letters():
for letter in ['a', 'b', 'c', 'd', 'e']:
print(f"Letter: {letter}")
time.sleep(1)
if __name__ == "__main__":
thread1 = threading.Thread(target=print_numbers)
thread2 = threading.Thread(target=print_letters)
thread1.start() # Start the first thread
thread2.start() # Start the second thread
thread1.join() # 用于阻塞當前線程,直到被調用的線程完成其執(zhí)行
thread2.join() # 用于阻塞當前線程,直到被調用的線程完成其執(zhí)行
print("All threads completed")
-
在這個示例中,創(chuàng)建了兩個線程對象,每個線程對象都關聯(lián)一個不同的任務函數(shù)(
print_numbers
和print_letters
)。然后啟動這兩個線程,并等待它們完成。最后,我們輸出一個提示,表示所有線程都已完成。 -
thread1
和thread2
是兩個線程對象,而thread1.join()
和thread2.join()
是在主線程中調用的。當調用這些方法時,主線程會阻塞,直到對應的線程(thread1
或thread2
)完成了它們的執(zhí)行。 -
thread1.join()
和thread2.join()
語句確保在兩個子線程執(zhí)行完成后,主線程才會輸出 “All threads completed” 這條消息。如果不使用join()
,主線程可能會在子線程還沒有完成時就繼續(xù)執(zhí)行,導致輸出消息的時機不確定。
1.4 補充:join()方法
-
在多線程編程中,
join()
方法用于阻塞當前線程,直到被調用的線程完成其執(zhí)行。具體來說,thread1.join()
表示當前線程(通常是主線程)會等待thread1
線程完成后再繼續(xù)執(zhí)行。 -
這種等待的機制可以確保主線程在所有子線程執(zhí)行完成后再繼續(xù)執(zhí)行,從而避免可能出現(xiàn)的線程之間的競爭條件和不確定性。這在需要等待所有線程完成后進行進一步操作或獲取線程執(zhí)行結果時非常有用。
1.5 并行、并發(fā)實現(xiàn)演示
- 并行執(zhí)行多個任務演示:
import threading
def task1():
print("Task 1 started")
# ... some code ...
print("Task 1 finished")
def task2():
print("Task 2 started")
# ... some code ...
print("Task 2 finished")
if __name__ == "__main__":
thread1 = threading.Thread(target=task1)
thread2 = threading.Thread(target=task2)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print("All tasks completed")
- 并發(fā)執(zhí)行演示:
import threading
import time
def task1():
print("Task 1 started")
time.sleep(2) # Simulate I/O operation
print("Task 1 finished")
def task2():
print("Task 2 started")
time.sleep(1) # Simulate I/O operation
print("Task 2 finished")
if __name__ == "__main__":
thread1 = threading.Thread(target=task1)
thread2 = threading.Thread(target=task2)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print("All tasks completed")
1.6 Thread參數(shù)傳遞使用演示
- 當使用
threading.Thread
創(chuàng)建線程對象時,可以通過args
、kwargs
和daemon
參數(shù)傳遞不同類型的信息給線程。 - 以下是針對每種參數(shù)的示例:
- 使用
args
參數(shù)傳遞位置參數(shù):
import threading
def print_numbers(start, end):
for i in range(start, end + 1):
print(f"Number: {i}")
if __name__ == "__main__":
thread1 = threading.Thread(target=print_numbers, args=(1, 5))
thread2 = threading.Thread(target=print_numbers, args=(6, 10))
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print("All threads completed")
- 使用
kwargs
參數(shù)傳遞關鍵字參數(shù):
import threading
def greet(name, message):
print(f"Hello, {name}! {message}")
if __name__ == "__main__":
thread1 = threading.Thread(target=greet, kwargs={"name": "Alice", "message": "How are you?"})
thread2 = threading.Thread(target=greet, kwargs={"name": "Bob", "message": "Nice to meet you!"})
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print("All threads completed")
- 使用
daemon
參數(shù)設置守護線程:
import threading
import time
def count_seconds():
for i in range(5):
print(f"Elapsed: {i} seconds")
time.sleep(1)
if __name__ == "__main__":
thread = threading.Thread(target=count_seconds)
thread.daemon = True # 設置線程為守護線程
thread.start()
# No need to join daemon threads, they will be terminated when the main thread ends
print("Main thread completed")
- 將線程
thread
設置為守護線程(daemon = True
)。這意味著當主線程結束時,守護線程也會被終止,而無需使用join()
等待它完成。
二 網絡編程
2.1 Socket初識
- Python的套接字(Socket)編程是一種基本的網絡編程技術,它可以在網絡上建立連接并進行數(shù)據傳輸。
- socket (簡稱 套接字) 是進程之間通信一個工具,進程之間想要進行網絡通信需要socket。Socket負責進程之間的網絡數(shù)據傳輸,是數(shù)據的搬運工。
2.2 客戶端和服務端
- 2個進程之間通過Socket進行相互通訊,就必須有服務端和客戶端
- Socket服務端:等待其它進程的連接、可接受發(fā)來的消息、可以回復消息
- Socket客戶端:主動連接服務端、可以發(fā)送消息、可以接收回復
2.3 創(chuàng)建socket對象詳解
-
在創(chuàng)建套接字對象時,通常是可以不指定參數(shù)的。如果沒有指定參數(shù),將會使用默認的參數(shù),這些參數(shù)在
socket
模塊中預先定義。默認情況下,socket
函數(shù)將創(chuàng)建一個 IPv4 的流式套接字。 -
例如,以下代碼將創(chuàng)建一個默認的 IPv4 TCP 套接字:
import socket # 創(chuàng)建一個默認的 IPv4 TCP 套接字 default_socket = socket.socket() # 后續(xù)代碼中可以使用 default_socket 進行操作
-
這種方式在很多情況下都是適用的,特別是當你只需要一個簡單的 IPv4 TCP 套接字時。
socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-
socket
:Python 的內置套接字模塊,它提供了在網絡上進行通信的基本功能。 -
socket.AF_INET
:表示套接字地址簇(Address Family),用于指定套接字使用的地址類型。AF_INET
表示使用 IPv4 地址。還可以使用AF_INET6
來表示 IPv6 地址。 -
socket.SOCK_STREAM
:表示套接字的類型。SOCK_STREAM
表示這是一個流式套接字,它基于 TCP 協(xié)議提供了可靠的、面向連接的、雙向的數(shù)據流傳輸。
-
-
綜合起來,
socket.socket(socket.AF_INET, socket.SOCK_STREAM)
創(chuàng)建了一個基于 IPv4 地址和 TCP 協(xié)議的流式套接字對象,你可以使用這個套接字對象來建立連接、發(fā)送和接收數(shù)據。 -
如果需要創(chuàng)建基于 UDP 協(xié)議的套接字,可以使用
socket.SOCK_DGRAM
,例如:udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
2.4 accept()方法詳解
-
accept()
方法是在服務器端套接字上調用的方法,用于接受客戶端的連接請求。它會阻塞程序,直到有客戶端嘗試連接到服務器,然后返回一個新的套接字用于與該客戶端進行通信,以及客戶端的地址信息。client_socket, client_address = server_socket.accept()
-
accept()
:接受客戶端的連接請求。當調用這個方法時,它會阻塞程序,直到有客戶端連接到服務器。一旦有連接請求到達,該方法將返回兩個值:一個是表示與客戶端通信的新套接字對象,另一個是客戶端的地址信息。 -
client_socket
:新創(chuàng)建的套接字對象,用于與連接的客戶端進行通信(可以使用這個套接字來接收和發(fā)送數(shù)據) -
client_address
:元組類型,包含客戶端的 IP 地址和端口號。例如,('192.168.1.100', 54321)
。
-
-
一般來說,服務器在一個循環(huán)中使用
accept()
方法,以便能夠接受多個客戶端的連接。每當有新的客戶端連接到服務器時,accept()
方法會返回一個新的套接字和客戶端的地址,然后服務器可以將新套接字添加到連接池,與客戶端進行通信。 -
注意:
accept()
方法在沒有連接請求時會一直阻塞程序。如果你希望設置超時或者非阻塞的連接等待,你可以在創(chuàng)建服務器套接字后設置相應的選項。這樣,在沒有連接請求時調用accept()
方法將立即返回,不會阻塞程序的執(zhí)行。 -
例如,在創(chuàng)建服務器套接字后可以使用以下代碼將其設置為非阻塞模式:
server_socket.setblocking(False)
2.5 發(fā)送信息方法詳解
-
send()
方法和sendall()
方法都用于在套接字上發(fā)送數(shù)據,但它們有一些不同之處。
send(data)
方法:
-
send()
方法是用于發(fā)送數(shù)據的基本方法,它接受一個字節(jié)流(bytes
)作為參數(shù),并嘗試將數(shù)據發(fā)送到連接的對方。 - 如果成功發(fā)送全部數(shù)據,該方法將返回發(fā)送的字節(jié)數(shù)。如果沒有發(fā)送完全部數(shù)據,可能返回一個小于請求發(fā)送數(shù)據的字節(jié)數(shù)。
- 如果在發(fā)送過程中出現(xiàn)問題(例如連接中斷),
send()
方法可能會引發(fā)異常。
sendall(data)
方法:
-
sendall()
方法也用于發(fā)送數(shù)據,但它更加健壯,會自動處理數(shù)據分片和重試。 -
無論數(shù)據有多大,
sendall()
方法會盡力將所有數(shù)據都發(fā)送出去,直到全部數(shù)據都被發(fā)送成功或發(fā)生錯誤。 -
方法不會立即返回,而是在所有數(shù)據都發(fā)送成功后才返回
None
。如果發(fā)生錯誤,它可能引發(fā)異常。 -
sendall()
方法在發(fā)送數(shù)據時會自動處理數(shù)據的分片,確保數(shù)據都被正確發(fā)送。 -
在大多數(shù)情況下,如果想要簡單地發(fā)送一小段數(shù)據,可以使用
send()
方法。然而,如果需要發(fā)送大量數(shù)據或者確保數(shù)據被完整、可靠地發(fā)送,那么使用sendall()
方法會更好,因為它會自動處理數(shù)據分片和錯誤處理。 -
使用
send()
方法:client_socket.send(b"Hello, server!")
-
使用
sendall()
方法:data = b"Hello, server!" client_socket.sendall(data)
2.6 Socket編程演示
-
服務器端代碼:
import socket # 1. 創(chuàng)建Socket對象 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2. 綁定服務器地址和端口 server_address = ('127.0.0.1', 12345) server_socket.bind(server_address) # 3. 開始監(jiān)聽端口 backlog=5 標識允許的連接數(shù)量,超出的會等待,可以不填,不填自動設置一個合理的值 server_socket.listen(5) print("Waiting for a connection...") # 4. 接收客戶端連接,獲得連接對象 client_socket, client_address = server_socket.accept() print(f"Connected to {client_address}") # 5. 客戶端連接后,通過recv方法 接收并發(fā)送數(shù)據 while True: data = client_socket.recv(1024).decode('utf-8') # recv接受的參數(shù)是緩沖區(qū)大小,一般給1024即可 # recv方法的返回值是一個字節(jié)數(shù)組也就是bytes對象,不是字符串,可以通過decode方法通過UTF-8編碼,將字節(jié)數(shù)組轉換為字符串對象 if not data: break print(f"Received: {data}") # 6. 通過client_socket對象(客戶端再次連接對象),調用方法,發(fā)送回復消息 msg = input("請輸入你要和客戶端回復的消息:") if msg == 'exit': break client_socket.sendall(msg.eccode("UTF-8")) # 7.關閉連接 client_socket.close() server_socket.close()
-
客戶端代碼:
import socket # 1.創(chuàng)建socket對象 client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2.連接服務器地址和端口 server_address = ('127.0.0.1', 12345) client_socket.connect(server_address) # 3.發(fā)送數(shù)據 while True: # 發(fā)送消息 msg = input("請輸入要給服務端發(fā)送的消息:") if msg == 'exit': break client_socket.sendall(msg.encode("UTF-8")) # 4.接收返回消息 recv_data = client_socket.recv(1024) # 1024是緩沖區(qū)的大小,一般1024即可。 同樣recv方法是阻塞的 print(f"服務端回復的消息是:{recv_data.decode('UTF-8')}") # 5.關閉連接 client_socket.close()
三 正則表達式
3.1 正則表達式概述
- 正則表達式,又稱規(guī)則表達式(Regular Expression),是使用單個字符串來描述、匹配某個句法規(guī)則的字符串,常被用來檢索、替換那些符合某個模式(規(guī)則)的文本。
- 正則表達式就是使用:字符串定義規(guī)則,并通過規(guī)則去驗證字符串是否匹配。
- 比如,驗證一個字符串是否是符合條件的電子郵箱地址,只需要配置好正則規(guī)則,即可匹配任意郵箱。通過正則規(guī)則:
(^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$)
即可匹配一個字符串是否是標準郵箱格式
3.2 Python正則表達式使用步驟
- 使用正則表達式的一些基本步驟和示例:
- 導入
re
模塊:
import re
- 使用
re.compile()
編譯正則表達式:
# `pattern_here`應該替換為實際正則表達式
pattern = re.compile(r'pattern_here')
- 使用編譯后的正則表達式進行匹配:
text = "This is an example text for pattern matching."
result = pattern.search(text)
if result:
print("Pattern found:", result.group())
else:
print("Pattern not found.")
3.3 正則的基礎方法
-
Python正則表達式,使用re模塊,并基于re模塊中基礎方法來做正則匹配。
-
匹配(Match):使用
match()
來從字符串的開頭開始匹配。匹配成功返回匹配對象(包含匹配的信息),匹配不成功返回空
result = pattern.match(text)
-
搜索(Search):使用
search()
來查找文本中的第一個匹配項。整個字符串都找不到,返回None
result = pattern.search(text)
-
查找所有(Find All):使用
findall()
來找到所有匹配項,并返回一個列表。找不到返回空list: []
results = pattern.findall(text)
-
替換(Replace):使用
sub()
來替換匹配項。
# `replacement`應該是希望替換匹配項的內容
new_text = pattern.sub(replacement, text)
-
分割(Split):使用
split()
來根據匹配項分割字符串。
parts = pattern.split(text)
-
在正則表達式中,你可以使用不同的元字符(例如
.
、*
、+
、?
、[]
、()
等)來構建復雜的模式,以便進行更精確的匹配。 -
演示使用正則表達式從文本中提取所有的電子郵件地址
import re
text = "Contact us at: john@example.com or jane@example.org for more information."
pattern = re.compile(r'\b[\w.-]+@[\w.-]+\.\w+\b')
email_addresses = pattern.findall(text)
for email in email_addresses:
print(email)
3.4 元字符匹配
- 單字符匹配
示例:
字符串s = "itheima1 @@python2 !!666 ##itcast3"
- 找出全部數(shù)字:
re.findall(r '\d', s)
- 字符串的r標記,表示當前字符串是原始字符串,即內部的轉義字符無效而是普通字符
- 找出特殊字符:
re.findall(r '\W', s)
- 找出全部英文字母:
re.findall(r '[a-zA-Z]', s)
-
[]
內可以寫:[a-zA-Z0-9]
這三種范圍組合或指定單個字符如[aceDFG135]
- 數(shù)量匹配
- 邊界匹配
- 分組匹配
四 遞歸
-
遞歸是一種編程技術(算法),即方法(函數(shù))自己調用自己的一種特殊編程寫法。在Python中,可以使用遞歸來解決許多問題,特別是那些可以被分解為相同或類似子問題的問題。
-
在使用遞歸時,需要確保定義遞歸基(base case),這是遞歸結束的條件,以避免無限循環(huán)。每次遞歸調用都應該將問題規(guī)模減小,使其朝著遞歸基的條件靠近。
-
使用遞歸計算階乘:
def factorial(n): if n == 0: return 1 # 遞歸基 else: return n * factorial(n - 1) # 遞歸調用 num = 5 result = factorial(num) print(f"The factorial of {num} is {result}")
-
factorial
函數(shù)通過不斷地調用自身來計算階乘。當n
達到遞歸基條件n == 0
時,遞歸結束,不再調用自身。
-
-
然而,遞歸并不總是最有效的解決方法,因為它可能會導致函數(shù)調用的嵌套層數(shù)過深,從而消耗大量的內存和處理時間。在一些情況下,使用循環(huán)或其他方法可能更有效。文章來源:http://www.zghlxwxcb.cn/news/detail-651283.html
-
在編寫遞歸函數(shù)時,要確保遞歸調用朝著遞歸基靠近,避免陷入無限循環(huán),同時考慮性能方面的問題。文章來源地址http://www.zghlxwxcb.cn/news/detail-651283.html
到了這里,關于探索Python編程的技巧:多線程魔法、網絡舞臺、正則魔法陣與遞歸迷宮的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!