搜尋
首頁後端開發Python教學Python中的上下文管理器

Python中把進入程式碼區塊前呼叫__enter__ 方法並在離開程式碼區塊後呼叫__exit__方法的物件作為上下文管理器,本文中我們就來深入解析Python中的上下文管理器,來看看上下文管理器的作用及用法:

1. 上下文管理器是什麼?

舉個例子,你在寫Python程式碼的時候常常將一系列運算放在一個語句區塊中:

(1)當某條件為真– 執行這個語句區塊

(2)當某條件為真– 迴圈執行這個語句區塊

有時候我們需要在程式在語句區塊中執行時保持某種狀態,並且在離開語句區塊後結束這種狀態。

所以,事實上上下文管理器的任務是 – 程式碼區塊執行前準備,程式碼區塊執行後再收拾。

上下文管理器是在Python2.5加入的功能,它能夠讓你的程式碼可讀性更強且錯誤更少。接下來,讓我們來看看該如何使用。

2. 如何使用上下文管理器?

看程式碼是最好的學習方式,來看看我們通常是如何開啟一個檔案並寫入」Hello World」?

filename = 'my_file.txt'
mode = 'w' # Mode that allows to write to the file
writer = open(filename, mode)
writer.write('Hello ')
writer.write('World')
writer.close()

1-2行,我們指明檔案名稱以及開啟方式(寫入)。

第3行,開啟文件,4-5行寫入“Hello world”,第6行關閉文件。

這樣不就行了,為什麼還需要上下文管理器?但是我們忽略了一個很小但是很重要的細節:如果我們沒有機會到達第6行關閉文件,那會怎麼樣?

舉個例子,磁碟已滿,因此我們在第4行嘗試寫入檔案時就會拋出異常,而第6行則根本沒有機會執行。

當然,我們可以使用try-finally語句區塊來進行包裝:

writer = open(filename, mode)
try:
  writer.write('Hello ')
  writer.write('World')
finally:
  writer.close()

finally語句區塊中的程式碼無論在try語句區塊中發生了什麼事都會執行。因此可以保證文件一定會關閉。這麼做有什麼問題?當然沒有,但當我們進行一些比寫入「Hello world」更複雜的事情時,try-finally語句就會變得醜陋無比。例如我們要開啟兩個文件,一個讀一個寫,兩個文件之間進行拷貝操作,那麼透過with語句能夠保證兩者能夠同時被關閉。

OK,讓我們把事情分解一下:

(1)首先,建立一個名為「writer」的檔案變數。

(2)然後,對writer執行一些操作。

(3)最後,關閉writer。

這樣是不是優雅多了?

with open(filename, mode) as writer:
  writer.write('Hello ') 
  writer.write('World')

讓我們深入一點,「with」是一個新關鍵字,總是伴隨著上下文管理器出現。 「open(filename, mode)」曾經在先前的程式碼中出現。 「as」是另一個關鍵字,它指涉了從「open」函數傳回的內容,並且把它賦值給了一個新的變數。 “writer”是一個新的變數名。

2-3行,縮排開啟一個新的程式碼區塊。在這個程式碼區塊中,我們能夠對writer做任意操作。這樣我們就使用了「open」上下文管理器,它保證我們的程式碼既優雅又安全。它出色的完成了try-finally的任務。

open函數既能夠當做一個簡單的函數使用,又能夠當作上下文管理器。這是因為open函數傳回了一個檔案類型(file type)變量,而這個檔案類型實作了我們之前用到的write方法,但是想要作為上下文管理器還必須實作一些特殊的方法,我會在接下來的小節介紹。

3. 自訂上下文管理器

讓我們來寫一個「open」上下文管理器。

要實現上下文管理器,必須實現兩個方法 – 一個負責進入語句區塊的準備操作,另一個負責離開語句區塊的善後操作。同時,我們需要兩個參數:檔案名稱和開啟方式。

Python類別包含兩個特殊的方法,分別名為:__enter__以及__exit__(雙底線作為前綴及後綴)。

當一個物件被用作上下文管理器時:

(1)__enter__ 方法將在進入程式碼區塊前被呼叫。

(2)__exit__ 方法則在離開程式碼區塊之後被呼叫(即使在程式碼區塊中遇到了異常)。

下面是上下文管理器的一個例子,它分別進入和離開程式碼區塊時進行列印。

class PypixContextManagerDemo:
 
  def __enter__(self):
    print 'Entering the block'
 
  def __exit__(self, *unused):
    print 'Exiting the block'
 
with PypixContextManagerDemo():
  print 'In the block'
 
#Output:
#Entering the block
#In the block
#Exiting the block

注意某些東西:

(1)沒有傳遞任何參數。
(2)在此沒有使用「as」關鍵字。
稍後我們將討論__exit__方法的參數設定。
我們如何傳遞參數給一個類別?其實在任何類別中,都可以使用__init__方法,在此我們將重寫它以接收兩個必要參數(filename, mode)。

當我們進入語句區塊時,將會使用open函數,正如第一個例子中那樣。而當我們離開語句區塊時,將關閉一切在__enter__函數中開啟的東西。

以下是我們的程式碼:

class PypixOpen:
 
  def __init__(self, filename, mode):
    self.filename = filename
    self.mode = mode
 
  def __enter__(self):
    self.openedFile = open(self.filename, self.mode)
    return self.openedFile
 
  def __exit__(self, *unused):
    self.openedFile.close()
 
with PypixOpen(filename, mode) as writer:
  writer.write("Hello World from our new Context Manager!")

#來看看有哪些變化:

(1)3-5行,透過__init__接收了兩個參數。

(2)7-9行,開啟檔案並返回。

(3)12行,当离开语句块时关闭文件。

(4)14-15行,模仿open使用我们自己的上下文管理器。

除此之外,还有一些需要强调的事情:

4.如何处理异常

我们完全忽视了语句块内部可能出现的问题。

如果语句块内部发生了异常,__exit__方法将被调用,而异常将会被重新抛出(re-raised)。当处理文件写入操作时,大部分时间你肯定不希望隐藏这些异常,所以这是可以的。而对于不希望重新抛出的异常,我们可以让__exit__方法简单的返回True来忽略语句块中发生的所有异常(大部分情况下这都不是明智之举)。

我们可以在异常发生时了解到更多详细的信息,完备的__exit__函数签名应该是这样的:

def __exit__(self, exc_type, exc_val, exc_tb)

这样__exit__函数就能够拿到关于异常的所有信息(异常类型,异常值以及异常追踪信息),这些信息将帮助异常处理操作。在这里我将不会详细讨论异常处理该如何写,以下是一个示例,只负责抛出SyntaxErrors异常。

class RaiseOnlyIfSyntaxError:
 
  def __enter__(self):
    pass
 
  def __exit__(self, exc_type, exc_val, exc_tb):
    return SyntaxError != exc_type


捕获异常:
当一个异常在with块中抛出时,它作为参数传递给__exit__。三个参数被使用,和sys.exc_info()返回的相同:类型、值和回溯(traceback)。当没有异常抛出时,三个参数都是None。上下文管理器可以通过从__exit__返回一个真(True)值来“吞下”异常。例外可以轻易忽略,因为如果__exit__不使用return直接结束,返回None——一个假(False)值,之后在__exit__结束后重新抛出。

捕获异常的能力创造了有意思的可能性。一个来自单元测试的经典例子——我们想确保一些代码抛出正确种类的异常:

class assert_raises(object):
  # based on pytest and unittest.TestCase
  def __init__(self, type):
    self.type = type
  def __enter__(self):
    pass
  def __exit__(self, type, value, traceback):
    if type is None:
      raise AssertionError('exception expected')
    if issubclass(type, self.type):
      return True # swallow the expected exception
    raise AssertionError('wrong exception type')

with assert_raises(KeyError):
  {}['foo']

5. 谈一些关于上下文库(contextlib)的内容

contextlib是一个Python模块,作用是提供更易用的上下文管理器。

(1)contextlib.closing

假设我们有一个创建数据库函数,它将返回一个数据库对象,并且在使用完之后关闭相关资源(数据库连接会话等)

我们可以像以往那样处理或是通过上下文管理器:

with contextlib.closing(CreateDatabase()) as database:
  database.query()

contextlib.closing方法将在语句块结束后调用数据库的关闭方法。

(2)contextlib.nested

另一个很cool的特性能够有效地帮助我们减少嵌套:

假设我们有两个文件,一个读一个写,需要进行拷贝。

以下是不提倡的:

with open('toReadFile', 'r') as reader:
  with open('toWriteFile', 'w') as writer:
    writer.writer(reader.read())

可以通过contextlib.nested进行简化:

with contextlib.nested(open('fileToRead.txt', 'r'),
            open('fileToWrite.txt', 'w')) as (reader, writer):
  writer.write(reader.read())

在Python2.7中这种写法被一种新语法取代:

with open('fileToRead.txt', 'r') as reader, \
    open('fileToWrite.txt', 'w') as writer:
    writer.write(reader.read())
contextlib.contextmanager

对于Python高级玩家来说,任何能够被yield关键词分割成两部分的函数,都能够通过装饰器装饰的上下文管理器来实现。任何在yield之前的内容都可以看做在代码块执行前的操作,而任何yield之后的操作都可以放在exit函数中。

这里我举一个线程锁的例子:

下面是线程安全写函数的例子:

import threading
 
lock = threading.Lock()
 
def safeWriteToFile(openedFile, content):
  lock.acquire()
  openedFile.write(content)
  lock.release()

接下来,让我们用上下文管理器来实现,回想之前关于yield和contextlib的分析:

@contextlib.contextmanager
def loudLock():
  print 'Locking'
  lock.acquire()
  yield
  print 'Releasing'
  lock.release()
 
with loudLock():
  print 'Lock is locked: %s' % lock.locked()
  print 'Doing something that needs locking'
 
#Output:
#Locking
#Lock is locked: True
#Doing something that needs locking
#Releasing

特别注意,这不是异常安全(exception safe)的写法。如果你想保证异常安全,请对yield使用try语句。幸运的是threading。lock已经是一个上下文管理器了,所以我们只需要简单地:

@contextlib.contextmanager
def loudLock():
  print 'Locking'
  with lock:
    yield
  print 'Releasing'

因为threading.lock在异常发生时会通过__exit__函数返回False,这将在yield被调用是被重新抛出。这种情况下锁将被释放,但对于“print ‘Releasing'”的调用则不会被执行,除非我们重写try-finally。

如果你希望在上下文管理器中使用“as”关键字,那么就用yield返回你需要的值,它将通过as关键字赋值给新的变量。下面我们就仔细来讲一下。

6.使用生成器定义上下文管理器
当讨论生成器时,据说我们相比实现为类的迭代器更倾向于生成器,因为它们更短小方便,状态被局部保存而非实例和变量中。另一方面,正如双向通信章节描述的那样,生成器和它的调用者之间的数据流可以是双向的。包括异常,可以直接传递给生成器。我们想将上下文管理器实现为特殊的生成器函数。事实上,生成器协议被设计成支持这个用例。

@contextlib.contextmanager
def some_generator(<arguments>):
  <setup>
  try:
    yield <value>
  finally:
    <cleanup>

contextlib.contextmanager装饰一个生成器并转换为上下文管理器。生成器必须遵循一些被包装(wrapper)函数强制执行的法则——最重要的是它至少yield一次。yield之前的部分从__enter__执行,上下文管理器中的代码块当生成器停在yield时执行,剩下的在__exit__中执行。如果异常被抛出,解释器通过__exit__的参数将之传递给包装函数,包装函数于是在yield语句处抛出异常。通过使用生成器,上下文管理器变得更短小精炼。

让我们用生成器重写closing的例子:

@contextlib.contextmanager
def closing(obj):
  try:
    yield obj
  finally:
    obj.close()

再把assert_raises改写成生成器:

@contextlib.contextmanager
def assert_raises(type):
  try:
    yield
  except type:
    return
  except Exception as value:
    raise AssertionError(&#39;wrong exception type&#39;)
  else:
    raise AssertionError(&#39;exception expected&#39;)

这里我们用装饰器将生成函数转化为上下文管理器!

更多Python中的上下文管理器相关文章请关注PHP中文网!


陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
Python vs.C:申請和用例Python vs.C:申請和用例Apr 12, 2025 am 12:01 AM

Python适合数据科学、Web开发和自动化任务,而C 适用于系统编程、游戏开发和嵌入式系统。Python以简洁和强大的生态系统著称,C 则以高性能和底层控制能力闻名。

2小時的Python計劃:一種現實的方法2小時的Python計劃:一種現實的方法Apr 11, 2025 am 12:04 AM

2小時內可以學會Python的基本編程概念和技能。 1.學習變量和數據類型,2.掌握控制流(條件語句和循環),3.理解函數的定義和使用,4.通過簡單示例和代碼片段快速上手Python編程。

Python:探索其主要應用程序Python:探索其主要應用程序Apr 10, 2025 am 09:41 AM

Python在web開發、數據科學、機器學習、自動化和腳本編寫等領域有廣泛應用。 1)在web開發中,Django和Flask框架簡化了開發過程。 2)數據科學和機器學習領域,NumPy、Pandas、Scikit-learn和TensorFlow庫提供了強大支持。 3)自動化和腳本編寫方面,Python適用於自動化測試和系統管理等任務。

您可以在2小時內學到多少python?您可以在2小時內學到多少python?Apr 09, 2025 pm 04:33 PM

兩小時內可以學到Python的基礎知識。 1.學習變量和數據類型,2.掌握控制結構如if語句和循環,3.了解函數的定義和使用。這些將幫助你開始編寫簡單的Python程序。

如何在10小時內通過項目和問題驅動的方式教計算機小白編程基礎?如何在10小時內通過項目和問題驅動的方式教計算機小白編程基礎?Apr 02, 2025 am 07:18 AM

如何在10小時內教計算機小白編程基礎?如果你只有10個小時來教計算機小白一些編程知識,你會選擇教些什麼�...

如何在使用 Fiddler Everywhere 進行中間人讀取時避免被瀏覽器檢測到?如何在使用 Fiddler Everywhere 進行中間人讀取時避免被瀏覽器檢測到?Apr 02, 2025 am 07:15 AM

使用FiddlerEverywhere進行中間人讀取時如何避免被檢測到當你使用FiddlerEverywhere...

Python 3.6加載Pickle文件報錯"__builtin__"模塊未找到怎麼辦?Python 3.6加載Pickle文件報錯"__builtin__"模塊未找到怎麼辦?Apr 02, 2025 am 07:12 AM

Python3.6環境下加載Pickle文件報錯:ModuleNotFoundError:Nomodulenamed...

如何提高jieba分詞在景區評論分析中的準確性?如何提高jieba分詞在景區評論分析中的準確性?Apr 02, 2025 am 07:09 AM

如何解決jieba分詞在景區評論分析中的問題?當我們在進行景區評論分析時,往往會使用jieba分詞工具來處理文�...

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

WebStorm Mac版

WebStorm Mac版

好用的JavaScript開發工具

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器