Org Mode的Refile功能

在Org Mode中经常会遇见org-refile这个命令。我这么多年一直不知道这个命令是干什么用的。最近重新捡起来用orgmode来管理自己的GTD事项,不自主的就捡起Org Mode – Organize Your Life In Plain Text! 这篇文章,想根据这篇文件来调整自己的GTD流程。

这篇文章里就谈到了org-refile这个命令,我终于明白这个命令就是方便的把一个节点移动到另一个节点下面的命令,快捷键是C-c C-w。但是这个命令有很大的灵活度,在默认的情况下它只能把当前的节点移动到当前文件的第一级节点下面。如果需要更灵活,那么要进行一些配置:

(setq org-refile-targets '((nil . (:level . 3))(org-agenda-files :maxlevel . 3)))

这句话的意思就是把org-refile命令的目标设置成当前文件和所有org-agenda文件的三个级别以内。这样在按了C-c C-w(或者 M-x org-refile)命令时候,在mini buffer里会列出来可以移动到的所有目标位置,用上下键可以选择,回车确定,就移动过去了。这样的好处是我们不用像操作文本一样拷贝粘贴,减少了错误率。

其实很简单。但是在我的实践中,我是用org-refile命令来归档的。然而,在上面的文章里,作者的GTD的操作是另外一种。就是他用org-capture命令把待办事项先简单地添到一个池子里,然后再把相应的todo项用org-refile命令移动到特别的项目文件里去。这是和我相反的一个思路,但是更合理。所以我也配置了<f9>-c作为我的org-capture命令进行快速的添加待办事项,然后再移动到不同的项目文件中。

简单记一笔,不断改进自己的工作流程。

投奔Prelude

好久以来我就是个Emacs用户,但是并不坚定。各种编辑器中都用用,有的时候为了一个特定的功能就会打开Sublime Text。现在我的电脑里装了Emacs、GVim、Sublime Text、VSCode、UltraEdit,这么多编辑器,还不算上PyCharm、IntelliJ一类的IDE。

Emacs和Vim的圣战中我一直站在Emacs一边,多年来也捅咕着自己的配置文件。有一年多,我是在用Spacemacs的,但是我的观念就是绝对不会设置成Vim模式。后来又听说了Doom Emacs,虽然说快一些,但是默认的Vim模式我也是很不喜欢。我喜欢那种按住CTRL键天花乱坠的章鱼感觉。

还有一点,在不同的配置之间切换也是我一直头疼的事情。既然Spacemacs、Doom Emacs只是GNU Emacs配合的一套配置文件,那应该有办法把它们装在不同的目录下,然后用脚本进行切换的。

这个礼拜在RSS上看到https://emacsredux.com/blog/2020/09/15/emacs-prelude-1-0/ 这一篇,原来Prelude终于升级到1.0啦。其中有一句话特别中我的意:

Prelude aims to enhance the classic Emacs experience without deviating a lot from it – e.g. it would never enable something like evil-mode (vim keybindings) by default and so on.

对吗,这才是Emacs该有的样子。Spacemacs还是Emacs吗?好怀疑。文档里有安装方法,可以把这套配置文件安装到一个制定的地方。

$ export PRELUDE_INSTALL_DIR="$HOME/.prelude.emacs.d" && curl -L https://github.com/bbatsov/prelude/raw/master/utils/installer.sh | sh

这样就行了。感觉上就是一个增强版的Vanilla Emacs啦。

后面一个多配置共享的问题,我在StackOverflow上搜到一个答案。https://emacs.stackexchange.com/a/44678,安装一个叫chemacs的工具。

$ git clone https://github.com/plexus/chemacs.git
$ cd chemacs
$ ./install.sh

然后在自己的家目录维护一个 .emacs-profiles.el 文件。在里面列举你的配置。

(("prelude" . ((user-emacs-directory . "~/.prelude.emacs.d")))
 ("spacemacs" . ((user-emacs-directory . "~/spacemacs"))))

最后就可以用相应的配置来启动Emacs啦。

$ emacs --with-profile prelude
$ emacs --with-profile spacemacs 

这样在保留Spacemacs的情况下又可以用Prelude了,很好。

YMCA Song

Young man, there’s no need to feel down
I said, young man, pick yourself off the ground
I said, young man, ’cause you’re in a new town 
There’s no need to be unhappy
Young man, there’s a place you can go
I said, young man, when you’re short on your dough
You can stay there, and I’m sure you will find 
Many ways to have a good time
It’s fun to stay at the YMCA
It’s fun to stay at the YMCA
They have everything for you men to enjoy
You can hang out with all the boys
It’s fun to stay at the YMCA
It’s fun to stay at the YMCA
You can get yourself clean, you can have a good meal
You can do what ever you feel 
Young man, are you listening to me? 
I said, young man, what do you want to be? 
I said, young man, you can make real your dreams
But you got to know this one thing
No man does it all by himself
I said, young man, put your pride on the shelf
And just go there, to the YMCA
I’m sure they can help you today
It’s fun to stay at the YMCA
It’s fun to stay at the YMCA
They have everything for you men to enjoy
You can hang out with all the boys 
It’s fun to stay at the YMCA
It’s fun to stay at the YMCA
You can get yourself clean, you can have a good meal 
You can do what ever you feel 
Young man, I was once in your shoes
I said, I was down and out with the blues
I felt no man cared if I were alive
I felt the whole world was so tight 
That’s when someone came up to me
And said, young man, take a walk up the street
There’s a place there called the YMCA
They can start you back on your way
It’s fun to stay at the YMCA
It’s fun to stay at the YMCA
They have everything for you men to enjoy
You can hang out with all the boys 
YMCA, it’s fun to stay at the YMCA
Young man, young man, there’s no need to feel down
Young man, young man, pick yourself off the ground
YMCA, it’s fun to stay at the YMCA
Young man, young man, are you listening to me
Young man, young man, what do you wanna be?
YMCA, you’ll find it at the YMCA
No man, young man, does it all by himself
Young man, young man, put your pride on the shelf
YMCA, and just go to the YMCA
Young man, young man I was once in your shoes
Young man, young man I was down with the blues, YMCA

欢乐颂

欢乐女神圣洁美丽
灿烂光芒照大地!
我们心中充满热情
来到你的圣殿里!
你的力量能使人们
消除一切分歧,
在你光辉照耀下面
四海之内皆成兄弟。

清明无锡游记录

记一下,否则就忘记了。

清明后一天,坐火车去无锡。

上次去无锡还是一九九零年,那个时候有可能成为一个无锡人,造化弄人,擦肩而过。

中午到无锡,先地铁到清名桥,吃了午饭,在一家中规中矩的餐馆,有烤鸭,有萝卜干,都很可口,还便宜。

下午去无锡博物馆。东区几层楼的动手区很棒,小朋友可以亲自动手玩玩,了解环境、地理、物理、人体构造……总的来说比上海自然博物馆好。

还去4D影院看了《羽龙》动画片,所谓的4D是会有喷雾,椅子也会配合场景动一动,感觉有人在后面踢你。小孩子还是可能害怕。总的来说寓教于乐,十几分钟也不拖沓,另外免费的,值得。

西区就是人文区了。有一个沙盘,清朝街景,有各种店铺,各种人物走在路上,还有人在运河边洗衣服,淘米。主街上有一队人阵仗很大的摆过来,队前还有华盖,估计是皇上南巡吧。

晚上和网友路人甲在一起吃饭,在座的还有她已经十几岁的女儿。我和女孩儿谈得倒是投机,网友啊,手机啊,情商不高的男生啊,王者荣耀啊,什么都还可以谈。临分别,还给女孩儿出了个小帖士,去读傅雷的《世界美术二十讲》,一班主任是美术老师,二是班主任建议不要读活人的书,也算是有的放矢,投其所好。

饭后路人甲还给安排了运河游船活动,短短一段运河之旅,我想到了杭州武林门到拱宸桥段的运河水上巴士。同样的运河,不同样的风物。下船后我们又在南长街上逛回住处。街上有小摊也有食肆,简直是河坊街和高银街的合体。有趣的一个事情,公共厕所的男女比重失调,男厕有时会划归女厕,门口有社区阿姨把守。

第二天早饭时候听隔壁桌在说鼋头渚的花已经被前两天的雨水打光了,交通又不方便,我们就放弃了去那的念头。叫了一辆滴滴快车我们就到了惠山古镇。那里有三个景点是连在一起的,寄畅园、天下第二泉和惠山寺。我们跟卖门票的说我们只去寄畅园,人家说里面是通的,要去就一起去。好吧,一起一起。

寄畅园又叫秦园,占地十五亩,好大。里面有各种假山乱石,还有亭台楼阁。别的不说,墙上的书刻就一定花了很多人力物力。我看着那些好看的行书草书,真是喜欢得不得了。当然,我已经不会天真地以为这些是这里原创的了,在杭州的胡雪岩故居我还震惊的发现苏东坡的寒食帖。

天下第二泉的泉眼已经变成了许愿池,满满的铺了一层硬币。纪念品商店的老板对好奇的游客讲述着瞎子阿炳的故事。里面还有一个小院子,据说在颐和园里有一个园子就是照搬这个的,里面有不少乾隆皇帝的御书。

还有朝房。看来惠山真是好地方,康乾二帝六次巡幸到此。文武百官在这里也就有了落脚的朝房。

下午回到南禅寺一带,吃了穆桂英美食,在茶叶一条街上逛一路,又到三凤桥买了面筋塞肉和排骨。走累了就在那边坐下,晚上吃了饭就火车回家了。

美好的无锡之行,感谢天气,感谢路人甲。下次还要再来,要去鼋头渚,要去拈花湾,想想应该会多住两天吧。

作为一个经验丰富的团队怎么办?

最近听到一个讲演,其中有一个部分讲到如果团队年纪大了该怎样发展下去,现在我记录一下。

经常有人问我们和H有什么区别,对吧?我们跟W团队有什么区别?我来讲讲我的看法。首先啊就是说,我们不再是一个年轻的团队。假设我们去跟W比,和H比,据说我们的平均年龄已经超过他们八岁九岁十岁。我们也有比较资深的,也有比较年轻的。从平均年龄来说我们已经超过八岁以上,到底是八岁九岁还是十岁我们不知道,估计差不多就是这个级别。我们和他们比的时候,我们不说把自己说成一个老年人团队,我们说我们是一个经验丰富的团队。而且我们是要往这个方向去的。

假设我们和H有合作的竞争,我们和W有合作的竞争,这种竞争就是我们要和他们的做法要不一样的。就是说我们不再和他们拼低成本。中国已经不是低成本了,上海已经不是低成本了。上海、北京已经不是低成本地方了。就是说,上海和北京能开公司的,就是有一定实力才能开得出来的。你看看上海的房价,对吧?各种成本。而且中央政府和上海政府把上海定义成金融方面发展的,亚洲金融中心。金融中心就是玩钱的,玩钱必然成本高。我们已经不是低成本的国家了,也不是低成本的site了。我记得刚开始我们和法国人做,他们说我们的成本是法国人的五分之一,六分之一。那现在我们是法国人成本的二分之一,到二分之一这种差别呢在成本上面很多时候会忽略掉,就是说很多时候做决策的时候我不再以成本作为一个很好的参考点。往往现在W的成本是我们的二分之一。他们的人力成本是极具优势的。所以说我们还听到说W的团队还在增长,这很正常的,因为他们还在吃人口红利。中国的红利在过去的二十年吃完了。

上海北京成为高成本的城市,同时呢刚才说从年龄结构来说我们又比他们年长。所以我们的路和他们走的路是不一样的。年轻团队,我指的是平均,他们年轻团队是可以犯错误,因为他们犯错误是可以用时间来解决。那对于我们年长团队和经验比较丰富的团队来说,我们要走的那条路是尽量避免犯错误,一次做对。我们不能去跟他们走一样的路。他们可以犯错误然后用加班十六个小时来解决这个问题。我们不能走这条路。我们要做的是让自己成为一个稳定的团队。

这个“稳定”指的是我们deliver出去的feature质量是非常高的,然后我们在处理项目的时候是很少犯错误甚至是不犯错误的。这样呢我们从单位时间工作效率来说我们要做得极高。有时候谈效率的时候别忘了是一种“伪效率”。什么叫伪效率呢?你一天工作十六个小时出来的一些东西和一天八小时出来的东西是不一样的。这个时候你如果论天谈效率这是不对的。因为有的团队他可以长时间地一天干十几个小时,我指的是平均。但是我们不是这种情况。对于我们来说,我们后面走的那条路是让自己成为一个技术能力稳定,deliver效率稳定。所以我们做事情的时候要以稳定、稳健输出的一个模式去做。这里面大家要正确理解哦,稳定不是慢。

稳定不是慢,而是说一次做对。这非常关键,这跟以前的时候那个项目理念是一样的。就是说一次做对,即便稍微做得慢,效率是最高的。这个就扯得远一点了,这和我们年龄结构也有点关系。我们都有孩子上小学了,也有上初中的。你做作业做错了,第二天订正还是要花很多时间。一次做对嘛,时间全省了嘛,一个道理。我不是说不许大家犯错误,而是说尽量的少犯错误。这样稳定的输出叫我们效率都很高。这个效率和输出不是建立在时间,不能建立在时间,比如我投多少时间进去,而是说我不犯错误,或者少犯错误,让我们的输出是非常稳定的,输出效率非常高。

我希望,这是对我们很高的要求。我们平时的持续的集成,deliver的时候,非常的认真仔细,符合流程,每个checkpoint,我们进一步完善DOD的过程。这是一连串的动作,稳定高效的输出是一连串的动作,稳定不是慢哦,稳定最终的结果是快。

我们要在这个环境里面,生存也好,竞争也好,发展得更好也好,我们要跟别人走的路是不一样的。我们的参考团队是德国。德国这个研发中心,当然我们还没有老到他们的程度,他们好像平均五十多了。但是这个是他们已经走过了一条路嘛,我们去看他们,他们的表现。德国人,其实,每次你跟他说快快快,他会快吗?不快的。但是他给你一个是一个,给你一个是一个。就是往这个方向去。我可能在commit的时候我不会非常激进,但是我一旦commit我很好的做到,质量非常高,效率非常高。我不是说用我先说行,然后用大量的加班去做到,不是,不是这条路。我们走的不是这条路,我们要往德国那一套发展。我们的能力是要持续提升的,每个人。技术能力,管理能力,各方面,团队沟通。然后呢,我们的输出是很稳定的。

我们有时候有些bug什么,哎呀,这个地方不小心。这个要避免。有些是技术能力,或是太复杂,有些scenario我想不到,这有的,很正常。因为现场太复杂了,我在设计的时候就是想不到的。我只有出去吃了亏,回来,哦,知道了,这地方要补一个。那这种呢叫持续学习。另一个呢,这个地方少写一个,哎呀,我失误了,这种是要避免掉的。这种在我们的工作中还是有的,不是很多,还是有的。我们要去避免这种失误或者错误。比如说不小心引起的,不在意引起的,这个呢,你说大了跟职业化有关,跟态度有关,但是这里边太复杂了我们不去说了。我们给出去的deliver,不管是feature还是bugfix的deliver,是稳定的,而且是高效的。尽量少犯错误,甚至不犯错误。这是我们要走的一条路,而且呢,我们自己个人能力团队能力要持续提升。我们要合理的安排自己的工作时间,我们要通过学习,互相学习,各种sharing,lesson learned,各种模式让自己团队不断提升,这是符合我们团队特性的一条路。

结合城市成本,结合年龄特征,稳定输出,是我们要走的一条路。

卡桑德拉

公司的希腊同事开发了一套小系统,用来预测代码出错的几率,取名叫卡桑德拉(Cassandra)。我有点觉得不吉利,毕竟她说实话,但是由于阿波罗的关系,世人都不相信她。

维基百科的词条是这里。稍微摘录一下词条。

出身背景

卡珊德拉别名为亚历珊德拉(Alexandra)。荷马(Homer)史诗《伊利亚特》(Iliad)说她美似金色的阿佛洛狄忒(Aphrodite),是特洛伊国王普里阿摩斯(Priam)最美丽的女儿,[1]是王后赫卡柏(Hecuba)的第三个女儿。欧里庇德斯(Euripides)的《特洛伊妇女》(The Trojan women)说她是阿波罗(Apollo)的祭司。[2]

预言能力

荷马诗注释家引传说卡珊德拉与赫勒诺斯(Helenus)为双生。其家在阿波罗‧提漠布里俄斯(Thymbraean)神庙祭祀庆祝,家人沾醉径去,将卡珊德拉与赫勒诺斯留在神庙。家人次日清醒,始至神庙寻找,见有神蛇以舌为二子洗耳,遂大惊叫,蛇潜入桂树枝间不见,卡珊德拉与赫勒诺斯遂得以预见未来。[3]

预言能力来源另一种说法为阿波罗的赐予。

神明诅咒

阿波罗诅咒卡珊德拉的理由,古希腊、罗马著作中有不同说法:

1.卡珊德拉在阿波罗神庙玩耍,玩累便睡着了。阿波罗试图拥抱卡珊德拉却遭到反抗,遂使她的预言不被相信,见于许癸努斯(Hyginus)的《神话指南》(Fabulae)。[4]

2.阿波罗多洛斯(Apollodorus)《书库》(The library)提及“阿波隆(阿波罗其它译名)想要与卡珊德拉交会,应许教给她占卜术,她学会了,可是不和他交会,于是阿波隆夺去了她占卜的使人信用的力量。”(周作人译)[5]

3.埃斯库罗斯阿伽门农》,依照罗念生译本,卡珊德拉允诺委身阿波罗却又使他失望,此后再也没有人相信她。[6]但在其他译本,“他是个摔角手,恩情往我身上喷。”(吕健忠译)“他将我扭抱,把我摔倒,吐喘甜蜜的欲火”(陈中梅译)[7]“他与我扭斗,同时他兴奋喘息。”(刘毓秀译)[8]似曾有肌肤之亲,后文提及决裂的原因是因为卡珊德拉承诺要为他生下子嗣却出言不果。中研院研究员李奭学先生〈长夜后的黎明--试论《奥勒斯提亚》的一则主题故事〉一文以圣婚概念解释剧中阿波罗与卡珊德拉间的纠葛。[9]

在劫难逃

王后赫卡柏怀帕里斯(Paris)时,梦见特洛伊陷于火海,普里阿摩斯与前妻之子埃萨科斯(Aesacus)释梦,预言帕里斯将导致特洛伊的覆灭。帕里斯遂被弃于伊达山,幸得牧人将之抚养成人。许癸努斯神话指南》描述帕里斯钟爱的一头牛被带走作为一场葬礼上的竞技的奖励品,帕里斯为夺回这头牛参与了竞技,赢得一切,甚至战胜自己的兄弟,得伊福彼斯(Deiphobus)发怒并袭击他,帕里斯窜至宙斯神坛,卡珊德拉预言宣称帕里斯是她的弟弟,普里阿摩斯认出他,帕里斯遂回归。

不和女神

忒提斯(Thetis)与珀琉斯(Peleus)的婚礼,未邀约不和女神厄里斯(Eris),厄里斯遂在婚礼上投下一颗金苹果,说给最美丽的,阿佛洛狄忒赫拉(Hera)与雅典娜(Athena)争夺金苹果,宙斯命赫尔墨斯(Hermes)带她们前往伊达山请帕里斯裁决,三名女神各允给以报酬,帕里斯最终将金苹果判给阿佛洛狄忒,因为阿佛洛狄忒承诺赐予他最美丽的女人,亦即海伦(Helen)。

祸国红颜

奥维德(Ovid)的《女英雄书信集》(Heroides)描述帕里斯远航至希腊之前,卡珊德拉预言这场航行将会带来大火。[10]达瑞斯•佛里癸俄斯(Dares Phrygius)的《特洛伊的沦陷》(The Fall of Troy)描述当卡珊德拉见到海伦时,她开始预言曾说过的收容海伦的恶果,直至普里阿摩斯下令将她带走并囚禁。[11]

十年鏖战

荷马伊利亚特》说俄斯鲁俄纽斯(Othruoneus)想娶卡珊德拉为妻而参战,后死于伊多墨纽斯(Idomeneus)之手;狄克提斯(Dictys Cretensis)说欧律皮罗斯(Eurypylus)是名杰出的战士,普里阿摩斯曾用很多礼物拉拢他,最后通过承诺让卡珊德拉与他联姻而赢得他的支持。荷马奥德赛》(Odyssey)中欧律皮罗斯死于涅俄普托勒摩斯(Neoptolemus)之手,奥德修斯(Odysseus)说欧律皮罗斯骁莽,是自己见过的最英俊的男子,仅次于卓著的门农(Memnon);[12]维吉尔(Virgil)《埃涅阿斯纪》(Aeneid)说科罗厄布斯(Coroebus)因为疯狂热烈地爱上卡珊德拉,作为普里阿摩斯未来的女婿,率领军队来支援特洛伊[13]

干城之死

荷马伊利亚特》中,普里阿摩斯赎回赫克托尔(Hector)尸体时,卡珊德拉站在城墙上,首先看见父亲还有兄长,呼唤特洛伊的男女迎接赫克托尔

木马屠城

希腊人十年鏖战,始终未能攻下特洛伊奥德修斯遂献策木马计。卡珊德拉与拉奥孔(Laocoon)都说木马内有一支军队,但他们的警告不被特洛伊人接受。

国破家亡

按照风俗,神庙不可被冒犯,木马屠城时特洛伊王室女性多藏匿于神坛。卡珊德拉藏身雅典娜神庙,她的遭遇有不同说法:阿波罗多洛斯书库》提及小埃阿斯把她强污,雅典娜的木像仰望着天;欧里庇得斯特洛伊妇女》则写小埃阿斯将她强行拉出神庙,并提及卡珊德拉仍为清白之躯。维吉尔埃涅阿斯纪》说科罗厄布斯为了营救卡珊德拉被佩涅勒乌斯杀死在雅典娜的神坛前。

希腊人分配战利品之后,昆图斯(Quintus SMYRNAEUS)《续荷马史诗》(Posthomerica)描述特洛伊妇女惊叹地看着卡珊德拉,忆及她关于毁灭的预言;但是面对她们的眼泪,卡珊德拉唯有充满怨恨的嘲笑,带着对故土毁灭的悲痛。[14]欧里庇得斯特洛伊妇女》当中,卡珊德拉提及只要洛克西阿斯阿波罗的别名)在,阿伽门农娶了她将比海伦的婚姻对他更有害,以隐晦的语言说这场婚姻将引起杀母之斗与阿特柔斯(Atreus)家族的衰败,并预述赫卡柏奥德修斯的结局。卡珊德拉为阿伽门农所得,奥维德爱经》(Ars Amatoria)说阿伽门农做了他俘虏的俘虏,[15]欧里庇得斯赫卡柏》歌唱队提及阿伽门农因卡珊德拉试图保全波吕克塞娜(Polyxena)的性命。[16]

赴死如归

阿伽门农之妻克吕泰涅斯特拉恨丈夫为求顺风献祭他们的女儿伊菲革涅亚(Iphigeneia),伙同情夫埃癸斯托斯(Aegisthus)密谋要杀害阿伽门农,卡珊德拉预见阿伽门农与自己至迈锡尼后必遭不测,隐忍不言。荷马奥德赛》提及她与阿伽门农在王宫的大厅中同时遇害;埃斯库罗斯阿伽门农》一剧,阿伽门农克吕泰涅斯特拉入内沐浴,卡珊德拉单独留在屋外,追述过往(包括帕里斯带来死亡的婚礼、特洛伊的覆灭还有阿特柔斯家族的罪孽),并预言阿伽门农与自己的死还有复仇者的复仇,最后请求歌唱队在自己死后见证复仇者的复仇,自己的话必不落空;塞涅加(Seneca)的《阿伽门农》以卡珊德拉与克吕泰涅斯特拉的对话完结:[17]

还有一本10 1/2章世界史 也提起了卡桑德拉,还有那个译制片卡桑德拉大桥

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-