pythonwith鎖
㈠ 深入解析python中的線程同步方法
深入解析Python中的線程同步方法
同步訪問共享資源
在使用線程的時候,一個很重要的問題是要避免多個線程對同一變數或其它資源的訪問沖突。一旦你稍不留神,重疊訪問、在多個線程中修改(共享資源)等這些操作會導致各種各樣的問題;更嚴重的是,這些問題一般只會在比較極端(比如高並發、生產伺服器、甚至在性能更好的硬體設備上)的情況下才會出現。
比如有這樣一個情況:需要追蹤對一事件處理的次數
counter = 0
def process_item(item):
global counter
... do something with item ...
counter += 1
如果你在多個線程中同時調用這個函數,你會發現counter的值不是那麼准確。在大多數情況下它是對的,但有時它會比實際的少幾個。
出現這種情況的原因是,計數增加操作實際上分三步執行:
解釋器獲取counter的當前值計算新值將計算的新值回寫counter變數
考慮一下這種情況:在當前線程獲取到counter值後,另一個線程搶佔到了CPU,然後同樣也獲取到了counter值,並進一步將counter值重新計算並完成回寫;之後時間片重新輪到當前線程(這里僅作標識區分,並非實際當前),此時當前線程獲取到counter值還是原來的,完成後續兩步操作後counter的值實際只加上1。
另一種常見情況是訪問不完整或不一致狀態。這類情況主要發生在一個線程正在初始化或更新數據時,另一個進程卻嘗試讀取正在更改的數據。
原子操作
實現對共享變數或其它資源的同步訪問最簡單的方法是依靠解釋器的原子操作。原子操作是在一步完成執行的操作,在這一步中其它線程無法獲得該共享資源。
通常情況下,這種同步方法只對那些只由單個核心數據類型組成的共享資源有效,譬如,字元串變數、數字、列表或者字典等。下面是幾個線程安全的操作:
讀或者替換一個實例屬性讀或者替換一個全局變數從列表中獲取一項元素原位修改一個列表(例如:使用append增加一個列表項)從字典中獲取一項元素原位修改一個字典(例如:增加一個字典項、調用clear方法)
注意,上面提到過,對一個變數或者屬性進行讀操作,然後修改它,最終將其回寫不是線程安全的。因為另外一個線程會在這個線程讀完卻沒有修改或回寫完成之前更改這個共享變數/屬性。
鎖
鎖是Python的threading模塊提供的最基本的同步機制。在任一時刻,一個鎖對象可能被一個線程獲取,或者不被任何線程獲取。如果一個線程嘗試去獲取一個已經被另一個線程獲取到的鎖對象,那麼這個想要獲取鎖對象的線程只能暫時終止執行直到鎖對象被另一個線程釋放掉。
鎖通常被用來實現對共享資源的同步訪問。為每一個共享資源創建一個Lock對象,當你需要訪問該資源時,調用acquire方法來獲取鎖對象(如果其它線程已經獲得了該鎖,則當前線程需等待其被釋放),待資源訪問完後,再調用release方法釋放鎖:
lock = Lock()
lock.acquire() #: will block if lock is already held
... access shared resource
lock.release()
注意,即使在訪問共享資源的過程中出錯了也應該釋放鎖,可以用try-finally來達到這一目的:
lock.acquire()
try:
... access shared resource
finally:
lock.release() #: release lock, no matter what
在Python 2.5及以後的版本中,你可以使用with語句。在使用鎖的時候,with語句會在進入語句塊之前自動的獲取到該鎖對象,然後在語句塊執行完成後自動釋放掉鎖:
from __future__ import with_statement #: 2.5 only
with lock:
... access shared resource
acquire方法帶一個可選的等待標識,它可用於設定當有其它線程佔有鎖時是否阻塞。如果你將其值設為False,那麼acquire方法將不再阻塞,只是如果該鎖被佔有時它會返回False:
if not lock.acquire(False):
... 鎖資源失敗
else:
try:
... access shared resource
finally:
lock.release()
你可以使用locked方法來檢查一個鎖對象是否已被獲取,注意不能用該方法來判斷調用acquire方法時是否會阻塞,因為在locked方法調用完成到下一條語句(比如acquire)執行之間該鎖有可能被其它線程佔有。
if not lock.locked():
#: 其它線程可能在下一條語句執行之前佔有了該鎖
lock.acquire() #: 可能會阻塞
簡單鎖的缺點
標準的鎖對象並不關心當前是哪個線程佔有了該鎖;如果該鎖已經被佔有了,那麼任何其它嘗試獲取該鎖的線程都會被阻塞,即使是佔有鎖的這個線程。考慮一下下面這個例子:
lock = threading.Lock()
def get_first_part():
lock.acquire()
try:
... 從共享對象中獲取第一部分數據
finally:
lock.release()
return data
def get_second_part():
lock.acquire()
try:
... 從共享對象中獲取第二部分數據
finally:
lock.release()
return data
示例中,我們有一個共享資源,有兩個分別取這個共享資源第一部分和第二部分的函數。兩個訪問函數都使用了鎖來確保在獲取數據時沒有其它線程修改對應的共享數據。
現在,如果我們想添加第三個函數來獲取兩個部分的數據,我們將會陷入泥潭。一個簡單的方法是依次調用這兩個函數,然後返回結合的結果:
def get_both_parts():
first = get_first_part()
seconde = get_second_part()
return first, second
這里的問題是,如有某個線程在兩個函數調用之間修改了共享資源,那麼我們最終會得到不一致的數據。最明顯的解決方法是在這個函數中也使用lock:
def get_both_parts():
lock.acquire()
try:
first = get_first_part()
seconde = get_second_part()
finally:
lock.release()
return first, second
然而,這是不可行的。裡面的兩個訪問函數將會阻塞,因為外層語句已經佔有了該鎖。為了解決這個問題,你可以通過使用標記在訪問函數中讓外層語句釋放鎖,但這樣容易失去控制並導致出錯。幸運的是,threading模塊包含了一個更加實用的鎖實現:re-entrant鎖。
Re-Entrant Locks (RLock)
RLock類是簡單鎖的另一個版本,它的特點在於,同一個鎖對象只有在被其它的線程佔有時嘗試獲取才會發生阻塞;而簡單鎖在同一個線程中同時只能被佔有一次。如果當前線程已經佔有了某個RLock鎖對象,那麼當前線程仍能再次獲取到該RLock鎖對象。
lock = threading.Lock()
lock.acquire()
lock.acquire() #: 這里將會阻塞
lock = threading.RLock()
lock.acquire()
lock.acquire() #: 這里不會發生阻塞
RLock的主要作用是解決嵌套訪問共享資源的問題,就像前面描述的示例。要想解決前面示例中的問題,我們只需要將Lock換為RLock對象,這樣嵌套調用也會OK.
lock = threading.RLock()
def get_first_part():
... see above
def get_second_part():
... see above
def get_both_parts():
... see above
這樣既可以單獨訪問兩部分數據也可以一次訪問兩部分數據而不會被鎖阻塞或者獲得不一致的數據。
注意RLock會追蹤遞歸層級,因此記得在acquire後進行release操作。
Semaphores
信號量是一個更高級的鎖機制。信號量內部有一個計數器而不像鎖對象內部有鎖標識,而且只有當佔用信號量的線程數超過信號量時線程才阻塞。這允許了多個線程可以同時訪問相同的代碼區。
semaphore = threading.BoundedSemaphore()
semaphore.acquire() #: counter減小
... 訪問共享資源
semaphore.release() #: counter增大
當信號量被獲取的時候,計數器減小;當信號量被釋放的時候,計數器增大。當獲取信號量的時候,如果計數器值為0,則該進程將阻塞。當某一信號量被釋放,counter值增加為1時,被阻塞的線程(如果有的話)中會有一個得以繼續運行。
信號量通常被用來限制對容量有限的資源的訪問,比如一個網路連接或者資料庫伺服器。在這類場景中,只需要將計數器初始化為最大值,信號量的實現將為你完成剩下的事情。
max_connections = 10
semaphore = threading.BoundedSemaphore(max_connections)
如果你不傳任何初始化參數,計數器的值會被初始化為1.
Python的threading模塊提供了兩種信號量實現。Semaphore類提供了一個無限大小的信號量,你可以調用release任意次來增大計數器的值。為了避免錯誤出現,最好使用BoundedSemaphore類,這樣當你調用release的次數大於acquire次數時程序會出錯提醒。
線程同步
鎖可以用在線程間的同步上。threading模塊包含了一些用於線程間同步的類。
Events
一個事件是一個簡單的同步對象,事件表示為一個內部標識(internal flag),線程等待這個標識被其它線程設定,或者自己設定、清除這個標識。
event = threading.Event()
#: 一個客戶端線程等待flag被設定
event.wait()
#: 服務端線程設置或者清除flag
event.set()
event.clear()
一旦標識被設定,wait方法就不做任何處理(不會阻塞),當標識被清除時,wait將被阻塞直至其被重新設定。任意數量的線程可能會等待同一個事件。
Conditions
條件是事件對象的高級版本。條件表現為程序中的某種狀態改變,線程可以等待給定條件或者條件發生的信號。
下面是一個簡單的生產者/消費者實例。首先你需要創建一個條件對象:
#: 表示一個資源的附屬項
condition = threading.Condition()
生產者線程在通知消費者線程有新生成資源之前需要獲得條件:
#: 生產者線程
... 生產資源項
condition.acquire()
... 將資源項添加到資源中
condition.notify() #: 發出有可用資源的信號
condition.release()
消費者必須獲取條件(以及相關聯的鎖),然後嘗試從資源中獲取資源項:
#: 消費者線程
condition.acquire()
while True:
...從資源中獲取資源項
if item:
break
condition.wait() #: 休眠,直至有新的資源
condition.release()
... 處理資源
wait方法釋放了鎖,然後將當前線程阻塞,直到有其它線程調用了同一條件對象的notify或者notifyAll方法,然後又重新拿到鎖。如果同時有多個線程在等待,那麼notify方法只會喚醒其中的一個線程,而notifyAll則會喚醒全部線程。
為了避免在wait方法處阻塞,你可以傳入一個超時參數,一個以秒為單位的浮點數。如果設置了超時參數,wait將會在指定時間返回,即使notify沒被調用。一旦使用了超時,你必須檢查資源來確定發生了什麼。
注意,條件對象關聯著一個鎖,你必須在訪問條件之前獲取這個鎖;同樣的,你必須在完成對條件的訪問時釋放這個鎖。在生產代碼中,你應該使用try-finally或者with.
可以通過將鎖對象作為條件構造函數的參數來讓條件關聯一個已經存在的鎖,這可以實現多個條件公用一個資源:
lock = threading.RLock()
condition_1 = threading.Condition(lock)
condition_2 = threading.Condition(lock)
互斥鎖同步
我們先來看一個例子:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import time, threading
# 假定這是你的銀行存款:
balance = 0
muxlock = threading.Lock()
def change_it(n):
# 先存後取,結果應該為0:
global balance
balance = balance + n
balance = balance - n
def run_thread(n):
# 循環次數一旦多起來,最後的數字就變成非0
for i in range(100000):
change_it(n)
t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))
t3 = threading.Thread(target=run_thread, args=(9,))
t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()
print balance
結果 :
[/data/web/test_python]$ python multhread_threading.py
0
[/data/web/test_python]$ python multhread_threading.py
61
[/data/web/test_python]$ python multhread_threading.py
0
[/data/web/test_python]$ python multhread_threading.py
24
上面的例子引出了多線程編程的最常見問題:數據共享。當多個線程都修改某一個共享數據的時候,需要進行同步控制。
線程同步能夠保證多個線程安全訪問競爭資源,最簡單的同步機制是引入互斥鎖。互斥鎖為資源引入一個狀態:鎖定/非鎖定。某個線程要更改共享數據時,先將其鎖定,此時資源的狀態為「鎖定」,其他線程不能更改;直到該線程釋放資源,將資源的狀態變成「非鎖定」,其他的線程才能再次鎖定該資源。互斥鎖保證了每次只有一個線程進行寫入操作,從而保證了多線程情況下數據的正確性。
threading模塊中定義了Lock類,可以方便的處理鎖定:
#創建鎖mutex = threading.Lock()
#鎖定mutex.acquire([timeout])
#釋放mutex.release()
其中,鎖定方法acquire可以有一個超時時間的可選參數timeout。如果設定了timeout,則在超時後通過返回值可以判斷是否得到了鎖,從而可以進行一些其他的處理。
使用互斥鎖實現上面的例子的代碼如下:
balance = 0
muxlock = threading.Lock()
def change_it(n):
# 獲取鎖,確保只有一個線程操作這個數
muxlock.acquire()
global balance
balance = balance + n
balance = balance - n
# 釋放鎖,給其他被阻塞的線程繼續操作
muxlock.release()
def run_thread(n):
for i in range(10000):
change_it(n)
加鎖後的結果,就能確保數據正確:
[/data/web/test_python]$ python multhread_threading.py
0
[/data/web/test_python]$ python multhread_threading.py
0
[/data/web/test_python]$ python multhread_threading.py
0
[/data/web/test_python]$ python multhread_threading.py
0
㈡ python中with語句的作用
with語句相當於你定義一個類的時候定義了__enter__()和__exit__()這個兩個方法。
在我們進行文件操作的的時候會用到open方法,後面有了with open以後就不再只使用open方法了,為什麼?因為with open方法簡單,我們不用再去管關閉文件了,即使中間發生異常,with open也會幫我們把文件關閉掉,以下示例演示下with open方法;
class File(object):
"""文件操作類"""
def __init__(self, filepath, mode):
self.filepath = filepath
self.mode = mode
def __enter__(self):
"""打開文件"""
self.file = open(self.filepath, self.mode)
print("打開文件")
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
"""關閉文件"""
print("關閉文件")
self.file.close()
if __name__ == '__main__':
with File('log.log', 'r') as file:
file.write("家啊")
可以看出來有了__enter__()和__exit__(),我們自定義的類也可以使用with了
㈢ 如何用python 中with 用法
要使用 with 語句,首先要明白上下文管理器這一概念。有了上下文管理器,with 語句才能工作。
下面是一組與上下文管理器和with 語句有關的概念。
上下文管理協議(Context Management Protocol):包含方法 __enter__() 和 __exit__(),支持
該協議的對象要實現這兩個方法。
上下文管理器(Context Manager):支持上下文管理協議的對象,這種對象實現了
__enter__() 和 __exit__() 方法。上下文管理器定義執行 with 語句時要建立的運行時上下文,
負責執行 with 語句塊上下文中的進入與退出操作。通常使用 with 語句調用上下文管理器,
也可以通過直接調用其方法來使用。
運行時上下文(runtime context):由上下文管理器創建,通過上下文管理器的 __enter__() 和
__exit__() 方法實現,__enter__() 方法在語句體執行之前進入運行時上下文,__exit__() 在
語句體執行完後從運行時上下文退出。with 語句支持運行時上下文這一概念。
上下文表達式(Context Expression):with 語句中跟在關鍵字 with 之後的表達式,該表達式
要返回一個上下文管理器對象。
㈣ python中with是什麼意思
關鍵字
with 的一般執行過程
一段基本的 with 表達式,其結構是這樣的:
with EXPR as VAR:
BLOCK
其中: EXPR 可以是任意表達式; as VAR 是可選的。
㈤ Python中的鎖都具有哪些
大致羅列一下:
一、全局解釋器鎖(GIL)
1、什麼是全局解釋器鎖
每個CPU在同一時間只能執行一個線程,那麼其他的線程就必須等待該線程的全局解釋器,使用權消失後才能使用全局解釋器,即使多個線程直接不會相互影響在同一個進程下也只有一個線程使用cpu,這樣的機制稱為全局解釋器鎖(GIL)。GIL的設計簡化了CPython的實現,使的對象模型包括關鍵的內建類型,如:字典等,都是隱含的,可以並發訪問的,鎖住全局解釋器使得比較容易的實現對多線程的支持,但也損失了多處理器主機的並行計算能力。
2、全局解釋器鎖的好處
1)、避免了大量的加鎖解鎖的好處
2)、使數據更加安全,解決多線程間的數據完整性和狀態同步
3、全局解釋器的缺點
多核處理器退化成單核處理器,只能並發不能並行。
4、GIL的作用:
多線程情況下必須存在資源的競爭,GIL是為了保證在解釋器級別的線程唯一使用共享資源(cpu)。
二、同步鎖
1、什麼是同步鎖?
同一時刻的一個進程下的一個線程只能使用一個cpu,要確保這個線程下的程序在一段時間內被cpu執,那麼就要用到同步鎖。
2、為什麼用同步鎖?
因為有可能當一個線程在使用cpu時,該線程下的程序可能會遇到io操作,那麼cpu就會切到別的線程上去,這樣就有可能會影響到該程序結果的完整性。
3、怎麼使用同步鎖?
只需要在對公共數據的操作前後加上上鎖和釋放鎖的操作即可。
4、同步鎖的所用:
為了保證解釋器級別下的自己編寫的程序唯一使用共享資源產生了同步鎖。
三、死鎖
1、什麼是死鎖?
指兩個或兩個以上的線程或進程在執行程序的過程中,因爭奪資源或者程序推進順序不當而相互等待的一個現象。
2、死鎖產生的必要條件?
互斥條件、請求和保持條件、不剝奪條件、環路等待條件
3、處理死鎖的基本方法?
預防死鎖、避免死鎖(銀行家演算法)、檢測死鎖(資源分配)、解除死鎖:剝奪資源、撤銷進程
四、遞歸鎖
在Python中為了支持同一個線程中多次請求同一資源,Python提供了可重入鎖。這個RLock內部維護著一個Lock和一個counter變數,counter記錄了acquire的次數,從而使得資源可以被多次require。直到一個線程所有的acquire都被release,其他的線程才能獲得資源。遞歸鎖分為可遞歸鎖與非遞歸鎖。
五、樂觀鎖
假設不會發生並發沖突,只在提交操作時檢查是否違反數據完整性。
六、悲觀鎖
假定會發生並發沖突,屏蔽一切可能違反數據完整性的操作。
python常用的加鎖方式:互斥鎖、可重入鎖、迭代死鎖、互相調用死鎖、自旋鎖大致羅列一下:
一、全局解釋器鎖(GIL)
1、什麼是全局解釋器鎖
每個CPU在同一時間只能執行一個線程,那麼其他的線程就必須等待該線程的全局解釋器,使用權消失後才能使用全局解釋器,即使多個線程直接不會相互影響在同一個進程下也只有一個線程使用cpu,這樣的機制稱為全局解釋器鎖(GIL)。GIL的設計簡化了CPython的實現,使的對象模型包括關鍵的內建類型,如:字典等,都是隱含的,可以並發訪問的,鎖住全局解釋器使得比較容易的實現對多線程的支持,但也損失了多處理器主機的並行計算能力。
2、全局解釋器鎖的好處
1)、避免了大量的加鎖解鎖的好處
2)、使數據更加安全,解決多線程間的數據完整性和狀態同步
3、全局解釋器的缺點
多核處理器退化成單核處理器,只能並發不能並行。
4、GIL的作用:
多線程情況下必須存在資源的競爭,GIL是為了保證在解釋器級別的線程唯一使用共享資源(cpu)。
二、同步鎖
1、什麼是同步鎖?
同一時刻的一個進程下的一個線程只能使用一個cpu,要確保這個線程下的程序在一段時間內被cpu執,那麼就要用到同步鎖。
2、為什麼用同步鎖?
因為有可能當一個線程在使用cpu時,該線程下的程序可能會遇到io操作,那麼cpu就會切到別的線程上去,這樣就有可能會影響到該程序結果的完整性。
3、怎麼使用同步鎖?
只需要在對公共數據的操作前後加上上鎖和釋放鎖的操作即可。
4、同步鎖的所用:
為了保證解釋器級別下的自己編寫的程序唯一使用共享資源產生了同步鎖。
三、死鎖
1、什麼是死鎖?
指兩個或兩個以上的線程或進程在執行程序的過程中,因爭奪資源或者程序推進順序不當而相互等待的一個現象。
2、死鎖產生的必要條件?
互斥條件、請求和保持條件、不剝奪條件、環路等待條件
3、處理死鎖的基本方法?
預防死鎖、避免死鎖(銀行家演算法)、檢測死鎖(資源分配)、解除死鎖:剝奪資源、撤銷進程
四、遞歸鎖
在Python中為了支持同一個線程中多次請求同一資源,Python提供了可重入鎖。這個RLock內部維護著一個Lock和一個counter變數,counter記錄了acquire的次數,從而使得資源可以被多次require。直到一個線程所有的acquire都被release,其他的線程才能獲得資源。遞歸鎖分為可遞歸鎖與非遞歸鎖。
五、樂觀鎖
假設不會發生並發沖突,只在提交操作時檢查是否違反數據完整性。
六、悲觀鎖
假定會發生並發沖突,屏蔽一切可能違反數據完整性的操作。
python常用的加鎖方式:互斥鎖、可重入鎖、迭代死鎖、互相調用死鎖、自旋鎖
㈥ python的with語句如何使用求詳解!
其實你可以這樣理解with ,就是可以自動維護一下上下文,並自動回收資源。
file = open("/tmp/foo.txt")
try:
data = file.read()
finally:
file.close()
這是不使用with的實現
with open("/tmp/foo.txt") as file:
data = file.read()
這是使用with的實現。
㈦ Python其實很簡單 第十五章 文件操作
在各種變數中保存的數據都是臨時的,隨著程序運行結束都會丟失。要做到數據長期有效,必須建立在磁碟中建立文件,將數據輸入到文件中並保存。需要獲取數據時需要打開文件讀取。
而我們自己建立的程序都是應用程序,從本質上講,應用程序是無法直接操作計算機的硬體的,譬如讀寫磁碟中文件,這就需要調用操作系統中的相應命令。接下來我們使用的Python內置函數open()、write()都是通過調用操作系統的相關命令才實現文件讀寫的,至於其中的細節,我們就不需要考慮了。
15.1創建和打開文件
在Python 中創建或打開文件,實際上是建立一個對象,該對象通過調用內置的open()函數創建或打開一個文件。
語法:
file object = open(filename [, mode][, buffering])
參數說明如下:
filename:file_name變數是一個包含了你要訪問的文件名稱的字元串值;
mode:mode決定了打開文件的模式:只讀,寫入,追加等。所有可取值見如下的完全列表。這個參數是非強制的,默認文件訪問模式為只讀(r)。
Buffering:如果buffering的值被設為0,就不會有寄存;如果buffering的值取1,訪問文件時會寄存行;如果將buffering的值設為大於1的整數,表明了這就是的寄存區的緩沖大小;如果取負值,寄存區的緩沖大小則為系統默認。
mode參數的參數值及說明
對於其中最難區別的r、r+、w、w+、a、a+幾個參數的區別總結如下,要特別注意指針的位置:
下面舉例說明open( )函數的使用方法。
例1:
>>> file=open(Ƈ.py')
如果文件「1.py」存在,則可以打開此文件;如果文件「1.py」不存在,則會出現如下提示:
Traceback (most recent call last):
File " ", line 1, in
file=open(Ƈ.py')
FileNotFoundError: [Errno 2] No such file or directory: Ƈ.py'
例2:
>>> file=open(Ɗ.py',』a+』)
雖然文件「4.py」不存在,但運行並未出現錯誤,參見上表,「a+」的含義是以讀寫模式打開文件,如果該文件已經存在,新內容將以追加方式寫入;如果該文件不存在,則新建文件用於寫入。查看文件夾,發現已經生成了一個新的文件4.py。
例3:
file=open('python.png','rb')
print(file)
運行結果:
這就是說,雖然Python可以打開一個圖片格式的文件,但print()並不能將其輸出,還需要第三方庫中模塊的相應方法去處理,如PIL中的open()f方法。
例4:
file = open("f.txt", "w",encoding='utf-8')
# 以只寫模式打開文件f.txt,編碼方式為utf-8
print( "文件名: ", file.name) # 輸出文件名
print( "是否已關閉 : ", file.closed) # 文件是否打開
print( "訪問模式 : ", file.mode) # 文件訪問模式
運行結果:
文件名: f.txt
是否已關閉 : False
訪問模式 : w
例5:
15.2關閉文件
打開文件使用後要及時關閉,以免造成不必要的破壞,同時也可以釋放內存。在Python中使用close()方法可以關閉文件。
語法格式:
file.close()
其中,file為文件對象。
15.3 with語句
with 語句適用於對資源進行訪問的場合,確保不管使用過程中是否發生異常都會執行必要的「清理」操作,釋放資源,比如文件使用後自動關閉、線程中鎖的自動獲取和釋放等。
with語句的語法格式如下:
with expression as target:
with-body
其中,expression用於指定一個表達式,譬如打開文件的open()函數。target用於指定一個變數,並且將expression的結果保存到該變數中,譬如文件對象file。with-body用於指定with語句體,譬如一些文件操作的相關語句,如果沒有要執行的語句體,則直接用pass語句代替。
假設python當前目錄下存在一個test.txt文件,其內容如下:
Python是一種解釋型語言: 這意味著開發過程中沒有了編譯這個環節。類似於PHP和Perl語言。
Python是互動式語言: 這意味著,您可以在一個 Python 提示符 >>> 後直接執行代碼。
Python是面向對象語言: 這意味著Python支持面向對象的風格或代碼封裝在對象的編程技術。
Python是初學者的語言:Python 對初級程序員而言,是一種偉大的語言,它支持廣泛的應用程序開發。
舉例如下:
with open('test.txt','r',encoding='utf-8') as file:
line=file.readline() # readline()方法可以讀取文件一行數據,接下來就會講到。
print(line)
運行結果如下:
Python是一種解釋型語言: 這意味著開發過程中沒有了編譯這個環節。類似於PHP和Perl語言。
而此時,我們給該段代碼with語句之外再增加一個讀取文件的語句,代碼如下:
with open('test.txt','r',encoding='utf-8') as file:
line=file.readline()
print(line)
line2=file.readline()
print(line2)
發現出現了如下錯誤提示:
Traceback (most recent call last):
File "C:/Users/zym/AppData/Local/Programs/Python/Python39/3.py", line 5, in
line2=file.readline()
ValueError: I/O operation on closed file.
意思是要讀取的文件已經被關閉了。
由此可知,當with語句運行結束後,被打開的test.txt文件就自動關閉了。
15.4讀取文件
在Python 中讀取文件的方法有:
1、讀取指定個數的字元
格式如下:
File.read([size])
其中,file為打開的文件對象。size為可選參數,可以指定要讀取的字元個數,省缺表示讀取所有內容。
在調用read()方法讀取文件內容時,文件必須是以r(只讀)或者r+(讀寫)方式打開。
如:
with open('test.txt','r',encoding='utf-8') as file:
txt=file.read() (或txt=file.read(10))
print(txt)
將讀取、輸出test.txt文件的全部內容(或前10個字元)。
2、移動文件的指針
對於剛打開的文件,文件指針總是指向文件頭的。也可以通過seek()方法將文件的指針移動到新的位置。
格式如下:
file.seek(offset[,whence])
其中,file表示已經打開的文件對象;offset用於指定移動的字元個數;whence表示從哪個位置起始計算個數,其值為0表示從文件頭開始計算,其值為1表示從當前位置開始計算,其值為2表示從文件尾開始計算,默認值為0。
例如:
with open('test.txt','r',encoding='utf-8') as file:
string=file.read(9)
print('取9個字元: '+string)
file.seek(2) #指針從文件頭開始移動2個字元
string=file.read(9) #從當前位置讀取10個字元
輸出結果:
取9個字元:
Python是一種
取9個字元:
thon是一種解釋
而下面的代碼會拋出錯誤:
with open('test.txt','r',encoding='utf-8') as file:
file.seek(2,1) #指針從當前位置開始移動2個字元
string=file.read(10) #從當前位置讀取10個字元
print('取10個字元: '+string)
錯誤提示為:
Traceback (most recent call last):
File "C:.py", line 7, in
file.seek(2,1) #指針從當前位置開始移動2個字元
io.UnsupportedOperation: can't do nonzero cur-relative seeks
原因在於,必須使用b模式(即rb)打開文件,才能使用whence參數。但是,b模式(二進制)不適合文本文件。對於test.txt這樣的文本文件,為了解決通過改變指針讀取任意位置字元,可以採用加一個位置變數的方法來存儲指針的值。
例如:
with open('test.txt','r',encoding='utf-8') as file:
#utf-8漢字與英文字元都佔一個字元
string='' #設置一個空字元串
pointer=0 #當前指針為0
str1=file.read(6) #讀取6個字元
pointer+=6 #指針變數後移6個字元
string+=str1 #string用來存放已讀取的字元
print('取6個字元: ',str1)
file.seek(pointer) #指針從文件頭開始移動2個字元
str1=file.read(8) #從當前位置讀取10個字元
pointer+=8 #指針跳過已讀取的字元
string+=str1
print('再取8個字元: ',str1)
print('所有讀取的字元: ',string)
print('當前指針所處的位置: ',pointer)
str1=file.read(1)
print('當前指針所處的字元: ',str1)
運行結果如下:
取6個字元:
Python
再取8個字元:
是一種解釋型語言
所有讀取的字元:
Python是一種解釋型語言
當前指針所處的位置:
14
當前指針所處的字元:
:
3、讀取一行數據readline()方法
語法格式:
file.readline()
例:
with open('test.txt','r',encoding='utf-8') as f:
string=f.read(1) # 讀取文件的第一個字元
if string != '': # 防止文件為空文件
lineno=0
while True:
line=f.readline()
if line != '':
lineno+=1
print('第'+str(lineno)+'行:'+line,end='')
# 因為每行都有自帶的分行符,print()語句不允許換行
else:
break # 出現空行時停止讀取
else:
print('要讀取的文件為空文件!')
運行結果:
第1行:ython是一種解釋型語言: 這意味著開發過程中沒有了編譯這個環節。類似於PHP和Perl語言。
第2行:Python是互動式語言: 這意味著,您可以在一個 Python 提示符 >>> 後直接執行代碼。
第3行:Python是面向對象語言: 這意味著Python支持面向對象的風格或代碼封裝在對象的編程技術。
第4行:Python是初學者的語言:Python 對初級程序員而言,是一種偉大的語言,它支持廣泛的應用程序開發。
4、讀取全部行命令readlines()方法
語法格式:
File.readlines()
該方法與read()方法一樣,在調用read()方法讀取文件內容時,文件必須是以r(只讀)或者r+(讀寫)方式打開。
例:
with open('test.txt','r',encoding='utf-8') as f:
txt=f.readlines()
print(txt)
運行結果:
['Python是一種解釋型語言: 這意味著開發過程中沒有了編譯這個環節。類似於PHP和Perl語言。 ', 'Python是互動式語言: 這意味著,您可以在一個 Python 提示符 >>> 後直接執行代碼。 ', 'Python是面向對象語言: 這意味著Python支持面向對象的風格或代碼封裝在對象的編程技術。 ', 'Python是初學者的語言:Python 對初級程序員而言,是一種偉大的語言,它支持廣泛的應用程序開發。 ']
從上面的運行結果可以看出,readlines()方法的返回值為一個字元串列表。所以,也可以以讀取列表元素的方法輸出。如下所示:
with open('test.txt','r',encoding='utf-8') as f:
txt=f.readlines()
for line in txt:
print(line,end='')
運行結果:
Python是一種解釋型語言: 這意味著開發過程中沒有了編譯這個環節。類似於PHP和Perl語言。
Python是互動式語言: 這意味著,您可以在一個 Python 提示符 >>> 後直接執行代碼。
Python是面向對象語言: 這意味著Python支持面向對象的風格或代碼封裝在對象的編程技術。
Python是初學者的語言:Python 對初級程序員而言,是一種偉大的語言,它支持廣泛的應用程序開發。
15.5 寫入文件內容
語法格式如下:
file.write(string)
其中,file為打開的文件對象,string為要寫入的字元串。
寫入文件內容時,文件必須以w(可寫)或a(追加)模式打開。否則,會拋出如下異常提示:
Traceback (most recent call last):
File "C:.py", line 2, in
f.write('人生苦短,我用Python!')
io.UnsupportedOperation: not writable
關於write()方法的用法舉例如下:
with open('test.txt','a',encoding='utf-8') as f:
f.write('人生苦短,我用Python!')
with open('test.txt','r',encoding='utf-8') as f:
txt=f.read()
print(txt)
運行結果:
Python是一種解釋型語言: 這意味著開發過程中沒有了編譯這個環節。類似於PHP和Perl語言。
Python是互動式語言: 這意味著,您可以在一個 Python 提示符 >>> 後直接執行代碼。
Python是面向對象語言: 這意味著Python支持面向對象的風格或代碼封裝在對象的編程技術。
Python是初學者的語言:Python 對初級程序員而言,是一種偉大的語言,它支持廣泛的應用程序開發。
人生苦短,我用Python!
可以看出,由於文件的打開方式為a模式(追加模式),寫入的內容被寫入到文件的末尾。
在Python中,文件操作方法里沒有類似於字元串內的計算長度、查找、替換、截取、分隔等方法,為什麼沒有?原因可能是文件的類型太復雜,譬如說二進制文件,上述操作的意義不大。如果僅僅要對文本文件進行上述操作,完全可以先把文件的內容讀取到字元串中,再用相應的字元串函數或方法去操作就可以了。譬如,要將test.txt文件中的字元串『Python』替換為』PHP』,則可以用如下代碼完成:
txt1=''
with open('test.txt','r',encoding='utf-8') as f:
txt1=f.read() #先將文件內容存入字元串txt1中
txt2=txt1.replace('Python','PHP') #將txt1中的'Python'替換為'PHP',並存入txt2
with open('test.txt','w',encoding='utf-8') as f:
f.write(txt2) #將字元串txt2的內容寫回到文件中
這里之所以分兩步打開文件(第一次為r模式,第二次為w模式),而沒有採用一次讀寫(r+、w+方式),因為那樣比較容易出錯。實踐證明,將文件的讀操作和寫操作分開其實是非常正確的選擇。
㈧ python with函數怎麼用
with在python中並不是函數,是一個關鍵詞語句,比如if就是關鍵詞語句。
with大多用來打開一個文檔。
比如:
with open('test.txt') as f:
f.read()
這樣就可以讀取名為test的文檔里的內容,並且使用with語句不需要我們手動再調用文件的close()方法來關閉文件,如果test.txt文檔對象不再被使用,with會自動關閉文檔。比較智能一點。
㈨ 揭秘 Python 中的 with 關鍵字
正文
你有沒有想過 with 語句是什麼以及我們為什麼使用它呢?請閱讀這篇文章!我們中的許多人在 Python 代碼中一遍又一遍地看到這個代碼片段: with open( 'Hi.text' , 'w' ) as f:
f.write( "Hello, there" )
但是,我們中的一些人不知道 with 有什麼用,以及為什麼我們需要在這里使用它。在此閱讀中,您將找到關於 with 可解決的幾乎所有問題。讓我們開始吧!
首先,讓我們考慮一下如果不使用 with 關鍵字我們需要做什麼。在這種情況下,我們需要先打開文件並 嘗試 執行 write 。不管成功與否,我們最好在 最後 關閉它,所以我們的代碼將如下所示:
f = open( 'Hi.text' , 'w' )
try :
f.write( 'Hello, there' )
finally :
f.close()
那麼, with 關鍵字有什麼用呢?它只是有助於將我們的 try..finally 代碼縮短為 with... 的單個語句!這就是 with 語句用法。
那麼,它到底是什麼?事實上, with 語句本身在 Python 中並沒有什麼特別之處,它只是 Python 中 上下文管理器 的一個特性。 上下文管理器 ,引用自 Python 官方文檔, 是一種讓您在需要時准確分配和釋放資源的方法 ,或者簡單來說: 當您在某些資源上做某事時縮短您的代碼片段 ,這意味著您可以自己定義 with 語句的用法!
我們如何做到這一點?嗯,很簡單,你只需要實現兩個 魔術函數 :一個叫做 __enter__ ,另一個叫做 __exit__ 。第一種方法是編寫一個實現這兩個函數的類,如下所示:
class My_file :
def __init__ (self, fname):
self.fname = fname
def __enter__ (self):
self.file = open(self.fname, 'w' )
return self.file
def __exit__ (self, exc_type, exc_val, exc_trace_back):
if self.file:
self.file.close()
在這里,我們創建了一個普通的 Python 類,實現了兩個魔術函數。注意這兩個函數的簽名: __enter__ 只接受 self ,而 __exit__ 將接受更多參數,示例中的這三個是標准形式。這樣,我們就可以直接使用:
with My_file( 'hello.txt' ) as f:
f.write( 'hello, world!' )
這里的 with 語句會先調用 __init__ 構造一個新對象,然後再調用 __enter__ 方法;最後,它會在代碼塊完成之前觸發 __exit__ 方法。所以,上面代碼的大致等價如下:
myfile = My_file( 'hello.txt' )
f = myfile.__enter__()
f.write( 'hello, world!' )
myfile.__exit(...)
實現 上下文管理器 的第二種方法是通過 裝飾器 ,如下:
1.你 import contextmanager from contextlib
2.你寫一個函數來實現你想要的 with 語句。
3.在函數上方添加一個裝飾器 @contextmanager 。
4.使用你的 with your_function !
根據上面的介紹,讓我們寫一個 裝飾器上下文管理器 !
from contextlib import contextmanager
@contextmanager
def my_file_open (fname):
try :
f = open(fname, 'w' )
yield f
finally :
print( 'Closing file' )
f.close()
with file_open( 'hi.txt' ) as f:
f.write( 'hello world' )
@contextmanager
def closing (f):
try :
f.write( "Finish writing" )
finally :
f.close()
with closing(open( "hi.text" )):
f.write( "hello world" )
例如,在上面的代碼中,我們可以直接調用 with close(your_way_of_getting_resource) ,在你下面寫的代碼塊即將完成之前( f.write("hello world") ),它會執行 try..finally 我們在上面定義的塊。另一個是使用 suppress 工具。我們知道,在很多情況下,如果我們嘗試獲取一些資源,很可能在打開文件時會出現 FileNotFoundException 等錯誤。在某些情況下,我們希望捕獲錯誤或抑制錯誤,以便程序繼續正常運行。 suppress 是我們可以抑制警告的一種方式。你需要做的就是弄清楚你想要抑制哪個異常,並編寫 with suppress(your_choice_of_exception) ,Python 將從這里開始處理它。在其他情況下,您可能只想在輸入 with 代碼塊時執行某些操作。在這種情況下, nullcontext 對你來說會很方便。 nullcontext 只會返回你在 __enter__ 函數中定義的東西,而不會做任何其他事情。如果您在 Python 中處理 async 操作以訪問資源,則 aclosure 是處理這種情況的實用工具。
總結
本文介紹了 with 語句的一些基本概念和用法及其底層工作原理。還有很多有趣的東西,請查看 Python 的 contextlib 文檔。最後,祝您能像往常一樣快樂學習和快樂編碼!
鏈接:https://python.plainenglish.io/demystifying-the-with-keyword-in-python-53be94e8b31a
你還有什麼想要補充的嗎?