8 PEP 343: The 'with' statement
The 'with' statement clarifies code that previously would use try...finally
blocks to ensure that clean-up code is executed. In this section, I'll discuss
the statement as it will commonly be used. In the next section, I'll examine the
implementation details and show how to write objects for use with this statement.
'with' 语句的功能是执行清理代码,它要比了我们过去用的 try ... finally清楚得多。
本章我会先探讨它是怎么用的,然后再研究其实现的细节,并且写一个能给 with 语句用的对象。
The 'with' statement is a new control-flow structure whose basic structure is:
'with' statement是一个新的流程控制结构,其基本的语法如下:
with expression [as variable]:
with-block
The expression is evaluated, and it should result in an object that supports the
context management protocol. This object may return a value that can optionally
be bound to the name variable. (Note carefully that variable is not assigned the
result of expression.) The object can then run set-up code before with-block is
executed and some clean-up code is executed after the block is done, even if the
block raised an exception.
(with语句会)先计算表达式,得到一个实现context management接口(这里pep作者使用了protocol,
而不是大家都比较熟悉的interface的术语)的对象。这个对象可能会返回一个值,而你则可以选择
是不是把这个值绑定到变量名("name variable")上。(请注意,这不是将表达式到值赋值给变量。)
然后对象先运行预备代码("set-up code"),再执行with-block,最后在离开block的时候,
即便是因为发生了异常,它也会执行清理代码。
To enable the statement in Python 2.5, you need to add the following directive
to your module:
要在Python 2.5里面用with语句,你得在module里面加上下面这行:
from __future__ import with_statement
The statement will always be enabled in Python 2.6.
等到了Python 2.6,你就不用再这么麻烦了。
Some standard Python objects now support the context management protocol and
can be used with the 'with' statement. File objects are one example:
有些Python对象已经实现了context management接口,因此现在就可以用在with语句里里。
File对象就是一例:
with open('/etc/passwd', 'r') as f:
for line in f:
print line
... more processing code ...
After this statement has executed, the file object in f will have been automatically
closed, even if the 'for' loop raised an exception part-way through the block.
只要这个语句运行结束,即便是因为 for 循环运行到一半的时候抛出了异常,
file对象f也会自动关闭。
The threading module's locks and condition variables also support the 'with' statement:
threading模块的lock和condition对象也支持with 语句
(据shhgs得理解,这里的locks表示threading里面的所有lock,如Lock, RLock都已经支持with了):
lock = threading.Lock()
with lock:
# Critical section of code
...
The lock is acquired before the block is executed and always released once the block
is complete.
执行block之前得先得到lock,然后执行完毕之后会释放锁。
The decimal module's contexts, which encapsulate the desired precision and rounding
characteristics for computations, provide a context_manager() method for getting a
context manager:
decimal模块提供了一个能返回context对象的context_manager()方法(请注意看注释,
至少Py2.5b1里面是没有这个方法,你得用get_manager() ),这个context对象
(context是上下文的意思)将计算的精度和近似要求封装了起来
(什么叫上下文?这才叫上下文!Perl的那个上下文根本就是假冒伪劣):
import decimal
# Displays with default precision of 28 digits
v1 = decimal.Decimal('578')
print v1.sqrt()
ctx = decimal.Context(prec=16)
#----------------------------------------
# 根据shhgs的试验,ctx对象是没有context_manager()方法的,
# 你得
# with ctx.get_manager() :
#----------------------------------------
with ctx.context_manager():
# All code in this block uses a precision of 16 digits.
# The original context is restored on exiting the block.
print v1.sqrt()
8.1 Writing Context Managers
Under the hood, the 'with' statement is fairly complicated.
Most people will only use 'with' in company with existing objects
and don't need to know these details, so you can skip the rest of
this section if you like. Authors of new objects will need to understand
the details of the underlying implementation and should keep reading.
'with'语句背后的实现是相当复杂的。绝大多数人只需要知道怎样把现成的对象用到with里面,
所以只要你觉得合适,完全可以跳过下面这段。但是如果你要写能给with用的对象,
那就得受累读下去了。
A high-level explanation of the context management protocol is:
context management 接口的大致概括如下:
The expression is evaluated and should result in an object called a
``context manager''. The context manager must have __enter__() and __exit__() methods.
(with语句先)计算表达式,拿到一个被成为context manager(上下文管理器)的对象。
这个context manager必须提供__enter__()和__exit__()方法。
The context manager's __enter__() method is called.
The value returned is assigned to VAR.
If no 'as VAR' clause is present, the value is simply discarded.
然后(with)再调用context manager的__enter__()方法。
如果语句里面还有'as VAR',那么它会顺手把(__enter__()所返回的)值赋给VAR。
如果没有'as VAR',这个值就丢了。
The code in BLOCK is executed.
然后执行BLOCK。
If BLOCK raises an exception, the __exit__(type, value, traceback) is called
with the exception details, the same values returned by sys.exc_info().
The method's return value controls whether the exception is re-raised:
any false value re-raises the exception, and True will result in suppressing it.
You'll only rarely want to suppress the exception, because if you do the author
of the code containing the 'with' statement will never realize anything went wrong.
如果BLOCK引发了异常,那么它会根据这个异常去调用__exit__(type, value, traceback)。
这个方法所返回的值同sys.exc_info()方法是相同的。这个返回值会告诉系统是不是再把
异常往上抛: false值表示再抛,True表示把它压下来。一般来说你不太需要去压制异常,
因为要是你在这里把异常压了下来,那些写with语句的人就永远也不知道这里还发生过异常了。
If BLOCK didn't raise an exception, the __exit__() method is still called,
but type, value, and traceback are all None.
如果BLOCK没有引发异常,with还是会调用__exit__()方法,只是这时type, value,
traceback都是None。
Let's think through an example. I won't present detailed code but will only
sketch the methods necessary for a database that supports transactions.
我们举一个例子。我不会给具体的代码,只是大致地划拉一下一个
支持transaction地数据库所必须提供的方法。
(For people unfamiliar with database terminology: a set of changes to the database
are grouped into a transaction. Transactions can be either committed, meaning that
all the changes are written into the database, or rolled back, meaning that the
changes are all discarded and the database is unchanged. See any database textbook
for more information.)
(这段话是为那些不熟悉数据库的朋友准备的: 所谓transaction就是把一组对数据库的修改捆绑起来。
你可以commit一个transaction,也就是说把这些修改全部写入数据库;也可以roll back,
也就是说把这些修改全都扔掉。随便找本数据库的书都会比我这里讲的详细。)
Let's assume there's an object representing a database connection.
Our goal will be to let the user write code like this:
架设这里有一个表示database connection的对象。我们的目标是要让用户能这样写代码:
db_connection = DatabaseConnection()
with db_connection as cursor:
cursor.execute('insert into ...')
cursor.execute('delete from ...')
# ... more operations ...
The transaction should be committed if the code in the block runs flawlessly or
rolled back if there's an exception. Here's the basic interface for DatabaseConnection
that I'll assume:
如果block运行无误的话,这个transaction就算是commit了。只要有异常,我们就roll back。
下面是我所预想的这个DatabaseConnection的基本接口。
class DatabaseConnection:
# Database interface
def cursor (self):
"Returns a cursor object and starts a new transaction"
def commit (self):
"Commits current transaction"
def rollback (self):
"Rolls back current transaction"
The __enter__() method is pretty easy, having only to start a new transaction.
For this application the resulting cursor object would be a useful result,
so the method will return it. The user can then add as cursor to their 'with'
statement to bind the cursor to a variable name.
__enter__()方法相当简单,只需要启动一个transaction就行了。对application来说,
cursor 对象会用得着,所以 __enter__() 得把它返回出去。这样用户就能在with语句里用cursor了。
class DatabaseConnection:
...
def __enter__ (self):
# Code to start a new transaction
cursor = self.cursor()
return cursor
The __exit__() method is the most complicated
because it's where most of the work has to be done.
The method has to check if an exception occurred.
If there was no exception, the transaction is committed.
The transaction is rolled back if there was an exception.
__exit__()最烦了,因为绝大多数的工作都是在这里干的。
它得检查是不是有异常。如果没有,它得负责commit,如果有它得roll back。
In the code below, execution will just fall off the end of the function,
returning the default value of None. None is false, so the exception will
be re-raised automatically. If you wished, you could be more explicit and
add a return statement at the marked location.
在下面这段代码里,真正重要的东西是函数的最后部分,返回一个None。None是false值,
所以异常会被重新抛出来。如果你想明确一下,可以在我注释的地方加一个return语句。
class DatabaseConnection:
...
def __exit__ (self, type, value, tb):
if tb is None:
# No exception, so commit
self.commit()
else:
# Exception occurred, so rollback.
self.rollback()
# return False
8.2 The contextlib module
The new contextlib module provides some functions and a decorator that are useful
for writing objects for use with the 'with' statement.
2.5新加的contextlib模块提供了一些用于编写供'with'语句使用的对象的方法和decorator。
The decorator is called contextfactory, and lets you write a single generator
function instead of defining a new class. The generator should yield exactly one value.
The code up to the yield will be executed as the __enter__() method,
and the value yielded will be the method's return value that will get bound to the
variable in the 'with' statement's as clause, if any. The code after the yield will be
executed in the __exit__() method. Any exception raised in the block will be raised
by the yield statement.
这个decorotor被称为contextfactory。有了它,你可以只写一个generator方法
而不用去定义一个新的类了。这个generator只能yield一个值。yield之前的代码表示__enter__()方法,
yield出来的值会绑定到 with 语句的 as 子句的变量,如果有的话。yield后面的代码是__exit__()要执行的。
此外,yield还会把block里面的异常再抛出来。
Our database example from the previous section could be written using this decorator as:
我们前面讲的数据库的例子可以这样改写:
from contextlib import contextfactory
@contextfactory
def db_transaction (connection):
cursor = connection.cursor()
try:
yield cursor
except:
connection.rollback()
raise
else:
connection.commit()
db = DatabaseConnection()
with db_transaction(db) as cursor:
...
The contextlib module also has a nested(mgr1, mgr2, ...) function
that combines a number of context managers so you don't need to write nested
'with' statements. In this example, the single 'with' statement both starts a database
transaction and acquires a thread lock:
contextlib模块还提供了一个能把多个context manager捆绑起来的nested(mgr1, mgr2, ... )函数,
这样你就不用把with语句嵌套起来了。下面这个例子里,我们只用一次with就启动了数据库的
transaction并获取了线程的锁。
lock = threading.Lock()
with nested (db_transaction(db), lock) as (cursor, locked):
...
Finally, the closing(object) function returns object so that it can be bound
to a variable, and calls object.close() at the end of the block.
最后,(contextfactory的)closing(object)函数会返回一个对象,这样你就可以把它绑定到变量上,
然后在block的最后(with会自动)调用object.close()了。
import urllib, sys
from contextlib import closing
with closing(urllib.urlopen('http://www.yahoo.com')) as f:
for line in f:
sys.stdout.write(line)
Subscribe to
Posts [Atom]