Python 的 with 语句(2)

我们经常既想利用with语句的便利,又不想很麻烦的写一个类来实现__enter__() 和 __exit__()方法,有什么比较现成的办法呢?

Python的标准库里引入了contextlib 模块可以解决这个问题。contextlib 模块提供了装饰器contextmanager,使用这个,可以对已有的生成器函数或者对象进行包装,加入对上下文管理协议的支持,避免了专门编写上下文管理器来支持 with 语句。

contextmanager 用于对生成器函数进行装饰,生成器函数被装饰以后,返回的是一个上下文管理器,其 enter() 和 exit() 方法由 contextmanager 负责提供。被装饰的生成器函数只能产生一个值,否则会导致异常 RuntimeError,产生的值会赋值给 as 子句中的 target,如果使用了 as 子句的话。

from contextlib import contextmanager

@contextmanager
def demo():
    print '[Allocate resources]'
    print 'Code before yield-statement executes in __enter__'
    yield '*** contextmanager demo ***'
    print 'Code after yield-statement executes in __exit__'
    print '[Free resources]'

with demo() as value:
    print 'Assigned Value: %s' % value

例子的执行结果是:

[Allocate resources]
Code before yield-statement executes in __enter__
Assigned Value: *** contextmanager demo ***
Code after yield-statement executes in __exit__
[Free resources]

可以看到,生成器函数中 yield 之前的语句在 __enter__() 方法中执行,yield 之后的语句在 __exit__() 中执行,而 yield 产生的值赋给了 as 子句中的 value 变量。

需要注意的是,contextmanager 只是省略了 __enter__() / __exit__() 的编写,但并不负责实现资源的“获取”和“清理”工作;“获取”操作需要定义在 yield 语句之前,“清理”操作需要定义 yield 语句之后,这样 with 语句在执行 __enter__() / __exit__() 方法时会执行这些语句以获取/释放资源,即生成器函数中需要实现必要的逻辑控制,包括资源访问出现错误时抛出适当的异常。

贴一下contextmanager的实现。用的时候就当成一个修饰器(decorator)来用。

def contextmanager(func):
    """@contextmanager decorator.

    Typical usage:

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

    This makes this:

        with some_generator(<arguments>) as <variable>:
            <body>

    equivalent to this:

        <setup>
        try:
            <variable> = <value>
            <body>
        finally:
            <cleanup>

    """
    @wraps(func)
    def helper(*args, **kwds):
        return GeneratorContextManager(func(*args, **kwds))
    return helper

另外说一句。with 语句的管理上下文的能力在python中往往被定义成设计模式的一种。下面就是一个例子。其实就是实现上下文管理协议,但是名字叫ResourceAcquisitionIsInitialization(资源获取初始化)。只贴代码,不另外做解释了。

class Box(object):
    
    def __init__(self, name):
        self.name = name

    def __enter__(self):
        print("Box " + self.name + " Opened")
        return self

    def __exit__(self, exception_type, exception, traceback):
        all_none = all(
            arg is None for arg in [exception_type, exception, traceback]
            )
        if (not all_none):
            print("Exception: \"%s\" raised." %(str(exception)))
        print("Box Closed")
        print("")
        return all_none

#===============================================================
if (__name__ == "__main__"):
    with Box("tupperware") as simple_box:
        print("Nothing in " + simple_box.name)
    with Box("Pandora's") as pandoras_box:
        raise Exception("All the evils in the world")
    print("end")

-EOF-

Python 的 with 语句(1)

参考了 浅谈 Python 的 with 语句

最近组里在CoDe(Competence Development,能力培养)。花姐讲文件操作,例子里有如下的代码:

with open(r'demo.txt') as f:
    for line in f:
        print line
        # ...more code

语句很简单,就是把demo.txt文件打开,逐行打印出来。我感兴趣的是这里的with语句,这个用法很简洁,比下面的try … except 用法要少一些代码量。

f = open(r'demo.txt')
try:
    for line in f:
        print line
        # ...more code
finally:
    f.close()

可以看到,下面的这种用法代码稍微复杂一些,还涉及到了文件的关闭操作。而with语句是怎么避免了显性的关闭文件呢?更进一步,with这个语句是怎么实现的,它的目的和好处是什么呢?

对比着看,可以感觉到with做了一系列的操作,准备工作、用户代码、扫尾工作。准备工作就是打开文件并且返回文件句柄(是不是这么定义?),用户代码就是中间那一部分逐行打印。扫尾工作就是关闭文件。

再做一个类比。在用robotframework做自动化测试时,总会定义 suite setup 和 suite teardown,这就是保证测试的进入和退出都有一定的保护措施,进入时做准备,退出时打扫战场。Python 的 with 语句就是suite setup 和 suite teardown的作用,是Python 2.5以后引入的特性。

在Python 2.5里要通过

from __future__ import with_statement

才能使用with 语句,到了Python 2.6就可以直接用了,它从一个实验性的特性变成了内置的了。

直接了当说,with的原理是实现了一个上下文管理协议(Context Management Protocol),而这个协议的两个必要部分就是两个方法,__enter__() 和 __exit__(),任何支持该协议的对象要实现这两个方法。一目了然了吧,这两个方法可以对应robotframework里的suite setup 和 suite teardown

先看一个例子吧。

class DummyResource(object):
    def __init__(self, tag):
        self.tag = tag
        print 'Resource [%s]' % tag

    def __enter__(self):
        print '[Enter %s]: Allocate resource.' % self.tag
        return self	

    def __exit__(self, exc_type, exc_value, exc_tb):
        print '[Exit %s]: Free resource.' % self.tag
        if exc_tb is None:
            print '[Exit %s]: Exited without exception.' % self.tag
        else:
            print '[Exit %s]: Exited with exception raised.' % self.tag
            return False

这段代码我是从最顶上那个帖子里抄的。可以做一些说明。

  • 这是一个叫DummyResource的类,提供了__enter__() 和 __exit__()两个方法,说明这个类是可以用with语句来调用的。另外从类名我们可以感觉到,with语句的运用都是和资源相关的。什么是资源?文件,网络套接字,线程thread,等等这些都是资源。
  • __enter__()方法里,除了一句打印“山顶的朋友,我在这里”,就是一句返回语句:
class DummyResource(object):
    # ...
    def __enter__(self):
        print '[Enter %s]: Allocate resource.' % self.tag
        return self	
    # ...

返回了一个self!这是什么意思?就是把这个类的实例返回出去。这样

with context_expression [as target(s)]:
    with-body

中的as target(s)就可以得到这个值了。忘了说,target(s)之所以有或许复数,是因为__enter__()方法是可以返回多个值成为一个元组的。

  • __exit__() 是个更复杂的方法,用来定义退出时候的动作。在方法的定义中可以看到有额外的参数exc_type、exc_value 和 exc_tb。这三个都是和异常相关的。如果with语句包住的用户代码正常执行,那么这三个变量都是None;反之,如果出错了,可以简单通过exc_type来判断异常的类型,配合后面两个变量获得更多的异常信息,进一步做相应的处理。
  • __exit__()方法中更重要的是它的返回值。return False。False的意思是“我还没有处理完,我要把这个异常继续抛出去,你们外面的代码去抓住它继续处理吧”。如果心满意足的,或者心怀鬼胎的觉得已经处理完了,就返回个真值,外面的代码就不会知道有异常发生过。很多问题的根本原因就这么湮没了。:-(

结合这个类定义,看看实用的调用例子吧。
小乖乖:

with DummyResource('Normal'):
    print '[with-body] Run without exceptions.'

和不乖的:

with DummyResource('With-Exception'):
    print '[with-body] Run with exception.'
    raise Exception
    print '[with-body] Run with exception. Failed to finish statement-body!'

小乖乖的输出是这样的:

Resource [Normal]
[Enter Normal]: Allocate resource.
[with-body] Run without exceptions.
[Exit Normal]: Free resource.
[Exit Normal]: Exited without exception.

很好,按部就班,只要读得懂类代码就很容易看出这些输出。下面看一下“小淘气”的输出呢。

Resource [With-Exception]
[Enter With-Exception]: Allocate resource.
[with-body] Run with exception.
[Exit With-Exception]: Free resource.
[Exit With-Exception]: Exited with exception raised.

Traceback (most recent call last):
  File "G:/demo", line 20, in <module>
   raise Exception
Exception

除了读代码能看清的输出,还要说一两句。

  • 用户代码中是先抛出了异常后又打印了一句“[with-body] Run with exception. Failed to finish statement-body!”。在输出中这句没有打印。这说明了__exit__()方法是清道夫,你们用户代码怎么乱糟糟,你先跑,抛出异常了,就别接着往下走了,我留下来打扫战场。
  • 输出中的“Traceback (most recent call last):”一段是解释器抛出的异常,在__exit__()方法中的开关就是最后的那个返回否值。如果是返回真值,这段Traceback是不会输出的。

最后,说说最初的那个花姐举出来的例子。

with open(r'demo.txt') as f:
    for line in f:
        print line
        # ...more code

打开一个Python的IDE(我用的是Pycharm CE),可以点到open的定义,就是一句话。

def open(name, mode=None, buffering=None): # real signature unknown; restored from __doc__
    """
    open(name[, mode[, buffering]]) -> file object
    
    Open a file using the file() type, returns a file object.  This is the
    preferred way to open a file.  See file.__doc__ for further information.
    """
    return file('/dev/null')

仅仅返回一个file实例。继续点过去,就豁然开朗了。

class file(object):
     # ... some codes

    def __enter__(self): # real signature unknown; restored from __doc__
        """ __enter__() -> self. """
        return self

    def __exit__(self, *excinfo): # real signature unknown; restored from __doc__
        """ __exit__(*excinfo) -> None.  Closes the file. """
        pass
     # ... more codes

file 这个类 实现了__enter__()和__exit__()方法,那外面当然可以用with语句来建立文件操作的上下文。

其实实现上下文管理协议并不是每次都需要写一个类实现两个办法,还有其他的方法,会另开一篇写写。

-EOF-

狗屎皮聚会

简单记一笔。8月20日,苏州。

因为先要去酒店入住,稍晚到会场。后来才发现第一场Scala的分享已经完全错过了。

TR在讲Symfony。由于技术细节听不太懂,只能关注一些方法,比如:

  • 跟数据相关的操作往后拖,所有的api都返回json格式的结果,前面的操作可以有更灵活的选择。
  • 路由用正则表达式来进行限制,超出这个范围的不做理会。
  • 要构造测试样本数据,合理真实,并有确实的期望结果做参考。

TR在gitbook上发布了一本相关的电子书,大家又讨论了一下gitbook的生成等周边技能。

午饭吃湖南菜。

下午令狐从架构的角度讲了讲。我能记下的是:

  • 一定要有技术手段来限制客户那些操作不能做。
  • 一定要考虑到灾难,比如硬盘坏了,节点挂了,大楼炸了。不是说过度设计,但是要找到一个平衡的点。
  • 代码检查,静态检查。要有,要有给对方犯错的机会。(这是不是说我们都长大了?)
  • 升级自己的产品的时候要兼容以前的版本,旧用户发现不能继续使用了是不可以接受的。

接下来我给大家看了一个docker-elk的image,ELK 的一个小例子。我事先导入了一本小说,然后可以分析词频,仅此而已。昨天,公司里有个培训,一个CI工程师分享ELK使用。他就是在Jenkins上用ZMQ插件把数据都规整成一定格式,用json方式发给ELK,就有各种可以供分析的新tags。我准备在自己的Jenkins上也这么弄一弄。

最后三火演示了一些Mesos,Marathon的东西,虽然知道这是分布式的方向,但是也不是个人能玩的。

每年有这么一次交流的机会很好。期待明年。

见Cliff

三月九号晚上在星光二期的新疆菜见了一个长辈Cliff,十几年没见了,又是同行,谈得非常投机。

有几点感受。

  • 专注。他一直在一个行业里工作,从来没有因为软件是万金油而转行,专注做那一块。
  • 积累。他二十年了还在用MFC,大浪淘沙,现在会MFC的高手已经少而又少了,他就是一等一的高手。二十年来他写了很多能给自己用的类库,积木搭一搭就很快了。反而不用不断的跟进新技术。
  • 围棋还是很开发智力的,要坚持。

PyCharm两件事

这是一篇技术相关的笔记,如果没有兴趣请绕路。

由于工种的变化,我开始用python写一个比较大的工程。那小作坊式的编辑器就不适合我,PyCharm就是我的选择。

单位的操作系统是Windows 7,家里的是Windows XP,两个系统上用的都是 PyCharm Community Edition 3.1.3, 都遇到了一些问题,分别说。

家里Windows XP上PyCharm 装好,启动。 发现内置的Terminal不能用,是灰的。

一开始我还到Twitter上去问,没有人给我回音。后来我发现可以看到PyCharm后台的log,具体的位置在“c:\Documents and Settings\username\.PyCharm30\system\log\idea.log”。打开,查找“Terminal”,能看到一些java抛出的异常信息。向上看,能看到一条:

“Unable to load library ‘D:\USERS\Haijiang\tools\PyCharm313\lib\libpty\win\x86\libwinpty.dll”

好吧,估计就是这个dll出的错啦。google上查找,找到了https://code.google.com/p/android/issues/detail?id=62572 这个帖子。帖子里说的是 android-studio 碰到这个问题,鉴于PyCharm和android-studio用同样的IDE,那就试试看。在#3答案里面提供了一个链接,github上的: https://github.com/rprichard/winpty/downloads 里面有一些编译好的包下载,我下载了winpty-0.1.1-msys.zip 这个包,替换了原来的位置的dll和exe文件,名字改成原来的样子。

重启,Terminal用起来。

在办公室(Windows 7)遇见的问题倒是简单一些。我最近偏爱Solarized Light这个配色主题。在github上我找到了一个为PyCharm做的主题,在这里:https://github.com/sevas/pycharm-color-schemes 。用起来还不错,有一个小问题,就是Terminal里的配色默认的情况下文字和背景是一样的,只有在选中的情况下才能看出来原来那里是有字的。一开始我以为Terminal也是灰的呢,后来发现能用,只是暗夜里的乌鸦,墨墨黑。

通过和其他的配色文件进行比较,我发现有两个变量控制终端的前景色和背景色,CONSOLE_BLUE_OUTPUT 和 CONSOLE_GREEN_OUTPUT。看到这里哑然失笑有没有?!github上的那个主题是没有这两个变量的,加上,给不同的值,把配色的xml文件放到 “c:\Documents and Settings\username\.PyCharm30\config\colors\” 下面去,PyCharm里面选好,重启。Terminal就用起来舒服了。

改良过的主题文件我放在我的github页面上了,这里

纪念李文琛先生

m

 

这是LinkedIn上的一个截图,可惜这个页面该不会再更新了,消息也送达不到收信人了,李先生已经荣归天家了。

Man Sum为他们公司的产品提供支持,为我们提供帮助。渐渐的他和我们很多人都成了好朋友,这是一个谦逊善良的兄长,一个真正的朋友。

第一次见到Man Sum是2006年底。他和John第一次来我们这里访问。我那时还对这个领域很陌生,Man Sum给了我很大的帮助。

Man Sum总是耐心的听完我的问题,然后耐心的告诉我怎么做。有的时候我都觉得我提出的问题是很蠢,提出来多么的不应该,起码应该自己找找解决方案。可是Man Sum没有嘲笑我,没有指出应该先去RTFM或者STFW,而是撸起袖子帮我一起做,同时会一点一点的告诉我这个信息到哪里去找,如何找到相应的资料。

Man Sum总是很耐心的记录下所有的需求。他会说不能马上答复你,但是他总会给你一个说法。哪怕这件事情是多么的小,小到你已经忘记。

后来我有了Juha。Man Sum建议我多给孩子拍照。他自己也是个摄影爱好者,为了家里的狗狗拍了很多好看的照片。为了这些照片还制备了RAID等硬件。他是一个热爱生活的人,为了生活拼命的工作。

Man Sum帮我带了几次香港的奶粉,后来他跟我说杭州的海关都认识他了。有一次Juha生病,需要保婴丹,正好知道Man Sum第二天要来杭州,我就冒昧的在晚上打电话给他,他二话不说就出去买,回到家还电话我说运气好,在超市关门前一刻买到最后的两盒。我和小超都很感激他,一直说要吃一餐饭。可是就永远的错过了。要是能换Man Sum回来,哪怕一天,烧掉一个Justin Bieber我也不在乎的。

很多话到了指尖就是打不出来。我想,除了我一定会有别家公司的人在写纪念Man Sum的文字吧?我也在想,是Man Sum什么样的品格使他受到这么多人的尊敬呢?技术人员的路就是该像他这样踏实的走下去的。

这两天总想到Man Sum。总想着办公室的门打开,他清瘦的身影背着黑色的电脑包闪进来,总是这么希望。

李先生一路走好。

2013新年快乐

玛雅人说的世界末日其实是来过的了。只是在那一瞬间,世界这台高精度的计算机保存了所有的寄存器信息重启了。

2013年来了,新年快乐。

换了一个组,会更忙,加油。

Juha也三周岁多了,一定要上幼儿园的了,纳入了世俗教育的轨道,还真有点儿舍不得。

2012年的目标很多没有达成,连买电脑也没有实现。2013年的小小目标还是买电脑吧,树莓派算不算?

要快乐,一定要快乐。

二月

我不想留到2月29号再写,听说那天是女孩儿表白的日子。

二月总是很快就过去,一是大多在过年,二是确实短几天。

月头带Juha去打疫苗。只是针扎进去哼唧两声,然后就好了。孩子大了,真好。

Juha闹了几天不舒服,接着小超也头疼感冒流鼻涕了几天。难为她几年也没个替手,病都不敢生。我也得分身有术才好。

最好的朋友Izzy在2012年2月22日这么二的日子领证结婚了。他已经不常住在杭州回去了。中旬的时候约我去洗澡。在澡池里天南地北的聊。说到以前交往的一个女孩儿时他说:

我都不觉得她是我女朋友,因为我所有女朋友你都是见过的。

这简直是给我发人品过关的证书嘛,甩那些Ex杀手几条马路。

有几个同事离职了。移民武汉,移民澳洲。改变总是好的,看得我心痒痒。

我还好,嗯。

祝贺Sam的儿子西恺降生。

2011过去了……

2011大事:

  • 一月,没事儿。
  • 二月,过年。生病,过年期间打点滴。
  • 三月,没事儿。
  • 四月,父母六十大寿。去深圳参加小舅子婚礼。到罗湖口岸盖章。
  • 五月,配眼镜。
  • 六月,参加猛禽饭婚礼。
  • 七月,房租涨价到一季度7200元,缴ADSL包年费用。Izzy携眷访问杭州。
  • 八月,小美丽到杭州,我们一起吃饭洗脚。入手iPad2,终于有了苹果产品。
  • 九月,没事儿。
  • 十月,国庆节假期。同学结婚。见南桥。
  • 十一月,没事儿。
  • 十二月,小超的爷爷去世,去广东奔丧。Juha两周岁。

相关统计:

  • 买了《程序员》杂志12期,《读者》杂志24期,《译林》一期。三轮车上盗版书11本。
  • 麦当劳17次(小超参与其中两次);肯德基23次;必胜客17次。
  • 支付宝充值21次。买奶粉40+罐。

2012 todos:

  • 多赚点钱。
  • 健康生活。
  • 和家人多在一起,多点儿再多点儿。
  • 写点儿小程序小网站。
  • 老婆买的彩票中大奖,25块一次少少拿也好也好。
  • 给自己买台笔记本电脑。

That’s all.