Python 中 with用法及原理


前言

with 語句適用於對資源進行訪問的場合,確保不管使用過程中是否發生異常都會執行必要的“清理”操作,釋放資源,比如文件使用后自動關閉/線程中鎖的自動獲取和釋放等。

問題引出

如下代碼:

file = open("1.txt")
data = file.read()
file.close()

上面代碼存在2個問題:
(1)文件讀取發生異常,但沒有進行任何處理;
(2)可能忘記關閉文件句柄;

改進

try:
    f = open('xxx')
except:
    print('fail to open')
    exit(-1)
try:
    do something
except:
    do something
finally:
    f.close()

雖然這段代碼運行良好,但比較冗長。
而使用with的話,能夠減少冗長,還能自動處理上下文環境產生的異常。如下面代碼:

with open("1.txt") as file:
    data = file.read()

with 工作原理

(1)緊跟with后面的語句被求值后,返回對象的 –enter–() 方法被調用,這個方法的返回值將被賦值給as后面的變量;

(2)當with后面的代碼塊全部被執行完之后,將調用前面返回對象的 –exit–() 方法。

with工作原理代碼示例:

class Sample:
    def __enter__(self):
        print "in __enter__"
        return "Foo"
    def __exit__(self, exc_type, exc_val, exc_tb):
        print "in __exit__"
def get_sample():
    return Sample()
with get_sample() as sample:
    print "Sample: ", sample

運行結果:

in __enter__
Sample:  Foo
in __exit__

整個運行過程如下:

(1)enter()方法被執行;
(2)enter()方法的返回值,在這個例子中是”Foo”,賦值給變量sample;
(3)執行代碼塊,打印sample變量的值為”Foo”;
(4)exit()方法被調用;

【注】exit()方法中有3個參數, exc_type, exc_val, exc_tb,這些參數在異常處理中相當有用。
exc_type: 錯誤的類型
exc_val: 錯誤類型對應的值
exc_tb: 代碼中錯誤發生的位置

示例代碼:

class Sample():
    def __enter__(self):
        print('in enter')
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        print "type: ", exc_type
        print "val: ", exc_val
        print "tb: ", exc_tb
    def do_something(self):
        bar = 1 / 0
        return bar + 10
with Sample() as sample:
    sample.do_something()

輸出結果:

in enter
Traceback (most recent call last):
type:  <type 'exceptions.ZeroDivisionError'>
val:  integer division or modulo by zero
  File "/home/user/cltdevelop/Code/TF_Practice_2017_06_06/with_test.py", line 36, in <module>
tb:  <traceback object at 0x7f9e13fc6050>
    sample.do_something()
  File "/home/user/cltdevelop/Code/TF_Practice_2017_06_06/with_test.py", line 32, in do_something
    bar = 1 / 0
ZeroDivisionError: integer division or modulo by zero

Process finished with exit code 1

總結

實際上,在with后面的代碼塊拋出異常時,exit()方法被執行。開發庫時,清理資源,關閉文件等操作,都可以放在exit()方法中。

總之,with-as表達式極大的簡化了每次寫finally的工作,這對代碼的優雅性是有極大幫助的。

如果有多項,可以這樣寫:

With open('1.txt') as f1, open('2.txt') as  f2:
    do something

with語句的原理

  • 上下文管理協議(Context Management Protocol):包含方法  __enter__() 和 __exit__() ,支持該協議的對象要實現這兩個方法。
  • 上下文管理器(Context Manager):支持上下文管理協議的對象,這種對象實現了 __enter__() 和 __exit__() 方法。上下文管理器定義執行with語句時要建立的運行時上下文,負責執行with語句塊上下文中的進入與退出操作。通常使用with語句調用上下文管理器,也可以通過直接調用其方法來使用。

with語句的常用表達式:

with EXPR as VAR:  # EXPR可以是任意表達式
    BLOCK

其一般的執行過程是這樣的:

1、執行EXPR,生成上下文管理器context_manager;

2、獲取上下文管理器的 __exit()__ 方法,並保存起來用於之后的調用;

3、調用上下文管理器的 __enter__() 方法;如果使用了as子句,則將 __enter__() 方法的返回值賦值給as子句中的VAR;

4、執行BLOCK中的表達式;

5、不管是否執行過程中是否發生了異常,執行上下文管理器的 __exit__() 方法, __exit__() 方法負責執行“清理”工作,如釋放資源等。如果執行過程中沒有出現異常,或者語句體中執行了語句break/continue/return,則以None作為參數調用__exit__(None, None, None);如果執行過程中出現異常,則使用sys.exc_info得到的異常信息為參數調用__exit__(exc_type, exc_value, exc_traceback)

6、出現異常時,如果__exit__(type, value, traceback)返回False,則會重新拋出異常,讓with之外的語句邏輯來處理異常,這也是通用做法;如果返回True,則忽略異常,不再對異常進行處理。

自定義上下文管理器

 Pythonwith語句是提供一個有效的機制,讓代碼更簡練,同時在異常產生時,清理工作更簡單。

示例1:

# coding = utf-8
# 2019/7/19  Luckyxxt:有趣的事,Python永遠不會缺席!
#!/usr/bin/env python

class DBManager(object):
    def __init__(self):
        pass

    def __enter__(self):
        print('__enter__')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('__exit__')
        return True

def getInstance():
        return DBManager()

with getInstance() as dbManagerIns:
    print('with demo')

【注】with后面必須跟一個上下文管理器,如果使用了as,則是把上下文管理器的 __enter__() 方法的返回值賦值給 target,target 可以是單個變量,或者由“()”括起來的元組(不能是僅僅由“,”分隔的變量列表,必須加“()”)

代碼運行結果如下:

'''
__enter__
with demo
__exit__

'''

結果分析:當我們使用with的時候,__enter__方法被調用,並且將返回值賦值給as后面的變量,並且在退出with的時候自動執行__exit__方法。

 

示例2:

class With_work(object):
    def __enter__(self):
        """進入with語句的時候被調用"""
        print('enter called')
        return "xxt"

    def __exit__(self, exc_type, exc_val, exc_tb):
        """離開with的時候被with調用"""
        print('exit called')


with With_work() as f:
    print(f)
    print('hello with')

運行結果:

'''
enter called
xxt
hello with
exit called

'''

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM