1. 程式人生 > >十六、Python 上下文管理 Context Manager

十六、Python 上下文管理 Context Manager

要使用 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 之後的表示式,該表示式要返回一個上下文管理器物件。

語句體(with-body):with 語句包裹起來的程式碼塊,在執行語句體之前會呼叫上下文管

理器的 __enter__() 方法,執行完語句體之後會執行 __exit__() 方法。

#-----*-coding:utf-8-*-----
"""
Python’s with statement was first introduced five years ago, 
in Python 2.5. It’s handy when you have two related operations which you’d like to execute as a pair,
 with a block of code in between. The classic example is opening a file, manipulating the file, then closing it:

with open('output.txt', 'w') as f:
    f.write('Hi there!')
The above with statement will automatically close the file after the nested block of code. 
(Continue reading to see exactly how the close occurs.) 
The advantage of using a with statement is that it is guaranteed to close the file no matter how the nested block exits. 
If an exception occurs before the end of the block, 
it will close the file before the exception is caught by an outer exception handler.
 If the nested block were to contain a return statement, or a continue or break statement,
  the with statement would automatically close the file in those cases, too.
"""

f = open("numbers.txt", "w")
f.write("hello")
f.close()

with open("numbers.txt", "w") as f:
    f.write("hello")

f = open("numbers.txt")
print dir(f)

import os


class chdir:

    def __init__(self, dir):
        self.dir = dir
        print "__init__"

    def __enter__(self):
        print "__enter__"
        self.olddir = os.getcwd()
        os.chdir(self.dir)
        return self

    def __exit__(self, *a):
        print "__exit__"
        os.chdir(self.olddir)

print os.getcwd()
print "before with"
with chdir("/tmp") as x:
    print os.getcwd()
print "after with"
print os.getcwd()

'''
Problem Write a context manager capture_output to capture stdout inside a with block.

with capture_output() as buf:
    print "hello"

out = buf.getvalue()
print "captured", repr(out)

Hint: See StringIO.StringIO and sys.stdout.

Lets try to understand how to capture output without context managers.
'''

import sys
from StringIO import StringIO

oldstdout = sys.stdout
buf = StringIO()
sys.stdout = buf
print "hello"
print "world"
sys.stdout = oldstdout
print "Captured", repr(buf.getvalue())

class capture_output:
	def __init__(self):
		self.buf=StringIO()

	def __enter__(self):
		self.oldstdout=sys.stdout
		sys.stdout=self.buf
		return self.buf

	def __exit__(self,type,exc,traceback):
		sys.stdout=self.oldstdout

capture_output()
print "hello"

result:
['__class__', '__delattr__', '__doc__', '__enter__', '__exit__', '__format__', '__getattribute__', '__hash__', '__init__', '__iter__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'closed', 'encoding', 'errors', 'fileno', 'flush', 'isatty', 'mode', 'name', 'newlines', 'next', 'read', 'readinto', 'readline', 'readlines', 'seek', 'softspace', 'tell', 'truncate', 'write', 'writelines', 'xreadlines']
/Users/macbook/Proj/practise
before with
__init__
__enter__
/private/tmp
__exit__
after with
/Users/macbook/Proj/practise
Captured 'hello\nworld\n'
hello
[Finished in 0.1s]