with statement @Python

Table of Content:
  1. with语句
  2. 深入一点点
  3. 实例
  4. 扩展
    1. contextmanager
    2. closing
      1. ChangeLog

with语句

以最常见的文件操作为例,打开文件,必须有对应的关闭文件.
这样保证资源(文件句柄)不会泄漏.但是如何手动保证这一点,在复杂的程序中,就会是一个问题.

程序的逻辑可能比较复杂,而在有异常机制的语言中,控制流就更为复杂.

在C++中,类似的问题,我们有析构函数来保证这一步.在Python中,我们有with语句.

with open(filename) as fp:
    for line in fp.readlines():
        pass

这样在with程序执行完毕,或者异常跳出的时候,都可以保证打开的文件必然会关闭.

下面就介绍下Python中的with.

上面的代码直观的不需要解释.(这就是Python的显著优点啊)

with的语句格式如下:

with EXPR [as TARGET]:
    BLOCK

深入一点点

with语句其实是一种contextmanager.它的语句格式就如上所示.

在执行with语句是的执行顺序

- 执行表达式EXPR
- 注册contextmanager的__exit__方法
- 执行contextmanager的__enter__方法
- 如果有as,那么将EXPR的结果赋值给TARGET
- 执行后面BLOCK中的代码
- 执行__exit__方法

一个典型的示例程序contextMgr.py:

class myCtxMgr():
    def __init__(self, val):
        print "init"
        self.val = val
    def __enter__(self):
        print "enter"
        return self.val
    # Don't use keyword type as var name
    # as Guan XiQing's suggestion
    def __exit__(self, typ, value, traceback):
        print "leave"
        return

with myCtxMgr(5) as val:
    print val

__exit__方法带的众多参数,就是为了保证在异常的情况下面,依然可以处理.

执行结果如下:

init
enter
5
leave

实例

with语句看上去不错,但是有什么用处呢?

利用with语句可以简化我们的代码.特别是我们想进入一个上下文时候,做特定的操作.离开上下文的时候,做相应的逆操作.

上下文的英文就是context

最常见的锁操作,进来加锁,退出关锁.可惜Python中没有锁的概念,不能使用这个例子了.

在数据库的操作简单封装的时候,其实也有类似的需求.连接数据库,操作数据库,断开连接.这里的连接其实也是一种上下文.

下面放一个sqlite3的数据库操作的小例子 db.py

import sqlite3
import json


rootdir = "./"
db = rootdir + "db/gering.db"
table = "clst"
sqlRecent10 = "SELECT * FROM %s ORDER BY id DESC LIMIT 10;" % (table)
sqlInsertSQL = "INSERT INTO %s (clnum, json) VALUES (?,?);" % (table)
sqlMaxid = "SELECT * FROM %s ORDER BY id DESC LIMIT 1;" % (table)
sqlDelAll = "DELETE FROM %s;" % (table)


class openDB():

    def __init__(self, db):
        self.db = db

    def __enter__(self):
        self.conn = sqlite3.connect(self.db)
        self.c = self.conn.cursor()
        return self.conn, self.c

    def __exit__(self, typ, value, trackback):
        self.c.close()


def insertHist(clnum, jsonobj):
    with openDB(db) as (conn, csr):
        csr.execute(sqlInsertSQL, (clnum, json.dumps(jsonobj)))
        newid = csr.lastrowid
        conn.commit()
        return newid


def getRecent10():
    with openDB(db) as (_, csr):
        csr.execute(sqlRecent10)
        return csr.fetchall()


def getMaxID():
    with openDB(db) as (_, csr):
        csr.execute(sqlMaxid)
        try:
            ret = csr.fetchone()[0]
        except TypeError:
            ret = 0
        return ret


def delAll():
    with openDB(db) as (conn, csr):
        csr.execute(sqlDelAll)
        conn.commit()
        return True

扩展

Python的标准库中有一些关于with的扩展库contextlib,用起来也比较有意思.

contextmanager

contextmanager是一种修饰器,配合yield语句使用. tag.py

import contextlib

@contextlib.contextmanager
def htmlTag(tag):
    print "<%s>" % (tag)
    yield
    print "</%s>" % (tag)


with htmlTag("h1"):
    print "Header"

输出是什么,随手可以试一下.

利用contextmanager,我们也可以快速的实现第一个小例子.这个就留给大家实践吧.

closing

closing完全符合上面的关闭资源这一点的设计目的.首先closing假设其对象参数,含有close方法.

当代码块BLOCK执行完毕的时候,自动调用对象参数的close方法.

from contextlib import closing
import urllib

with closing(urllib.urlopen('http://www.google.com')) as page:
    for line in page:
        print line

ChangeLog

  • rename type var name to typ