Python之subprocess库二三事

这两天写代码真心各种不顺,写工程结果搞得代码库乱掉,写算法被一两处笔误折腾得生不如死,这状态还不如整天躺床上看公开课去好了。做个简单的笔记,记录一下被坑的经过,以及一些小的细节。

关于subprocess库的使用

在编写服务端程序的时候,有时我们需要调用第三方程序,并获取程序的输出。如果这个程序需要长时间地运行并且不断地输出,而且我们还要经常查询这个输出的话,这样就存在两种方式,要么实时地读取这个输出并直接返回给客户端,在客户端“刷屏”,要么就是每次客户端发送一个查询请求,服务端就把当前已有的输出处理一下后全都返回给客户端。一种比较hacky的做法是,在调用程序的时候,直接在shell里将输出重定向。这样当然也是一种方案,但是如果我们事先不知道哪里要存放日志文件,或者说由于多线程的原因,要同时有多个输出的话,还得做随机文件名生成之类的操作,未免显得不够简洁优雅。实际上,subprocess库就可以用来漂亮地解决这个问题。

我们知道subprocess是一个现代化的用于处理进程间消息传递的库,用以取代一些旧的混乱的写法,意思也就是说只用它就可以了。它在IO方面有几个比较重要的参数,就重定向stdin、stdout和stderr。我们有两种比较好的用法,一种是将其重定向到subprocess.PIPE,一种是将其直接重定向到一个文件句柄。其实本质上来看,这里指定的参数可以被看作是一个缓冲区,所以文件句柄当然可以被看作是缓冲区,这样做的效果就是所有输出直接被重定向到了文件里面。但是读取的时候,我们会发现,如果我们用了PIPE,那么我们是要从对应的Popen对象的文件句柄来读取数据(也就是p.stdout.read()),而不是从PIPE里面读取数据,因为PIPE它实际上是个IO流,基本上就可以理解为是个缓冲区一样的概念,它只是用来暂存数据,但是访问的话,还是要访问上述对应Popen对象的文件句柄。

但是如果我们把输出重定向到了文件句柄,那么直接读取这个文件句柄就可以了。注意,对于这种边写边读取的情况,首先要刷新写缓冲区,也就是fid.flush,这个不用说了,但是然后,一定要将文件指针倒回文件开头!这个其实也是显然的,但总是容易被忽略,因为每次写入完以后,指针已经指向文件末尾了,这时候再想读取数据是读不出来的,所以必须要倒回指针。

另一方面,如果我们希望实时地读取输出并立刻打印出来或者保存起来,那么这时候我们就不得不使用PIPE了。此时,程序的行为是否正常取决于那个被调用的程序是否及时刷新了缓冲区。如果是的话,那么我们直接就可以通过不断调用readline(),或者是将readline函数用iter转换为一个可迭代对象访问来获取输出。为什么被调用程序及时刷新缓冲区如此重要呢?因为在Python的IO模型中,如果数据还没有输出(即还在缓冲区中),那么对于试图读取数据的操作,并不会返回EOF,而是会将进程阻塞。这样,就相当于程序不得不一直等待着这个未完成的读取操作,直到它终于把该输出的东西都吐出来。很多情况下,程序直到运行结束以后,才会真正开始输出。

shell=True和su - root

subprocess.Popen()的这个参数用来指明是否需要调用一个shell,并从中来执行这个程序。一般而言,不建议启用这个参数,理由是不同操作系统平台的Shell配置有着很大区别,这样做会导致较严重的平台依赖性。但问题在于,某些程序非常依赖于Shell预设的环境变量,如果缺乏了这些变量,就会导致程序根本无法正确运行。比如我这次写的这个包装Coursera下载器的web服务,问题就在于如果没有在shell环境中调用下载器的话,显然是找不到aria2的执行路径的,这一点我刚刚才想明白。很多这样的奇葩问题非常难以发现,明明在命令行下调用是正确的啊!这种情况下,我们就不得不先把原本的参数列表转换为命令语句,然后调用shell来执行对应语句了。

同样的问题也存在于Linux的at和cron服务中,在执行指令的时候,也会遇上这种明明自己在shell下执行是对的,但是一旦定时执行就会没反应/出错。但是这些服务在设定命令的时候可没有地方指定说要在某个登录后的shell中运行,那怎么来处理呢?其实做法很Tricky,利用su命令的“-”参数,在定时执行的时候会打开一个以指定用户登陆后的shell环境,并在其中执行代码。这样就完全相当于手动在shell中执行命令,也就肯定不会出差错了。

HTML模版的自动转义

写点web项目之后就会发现,HTML模版真心是个降低工作量、减小代码冗余的好东西。不同框架的模版有着自己独特的扩展语法,虽然用起来都是糙快猛,但是我们最需要注意的就是,什么情况下输入模版的用户变量会被转义,以及怎样关闭转义。对于web.py的自带模版系统,我们使用$来引用一个变量,实际上这个过程会自动转义变量中的HTML符号。如果我们不需要这个特性,可以改用$:来引用变量,就能够关闭自动转义了。

这个地方真心是个坑,一开始还以为自己代码的传递变量部分写得哪里有问题,怎么会导致HTML不解释呢?最后打开Chrome控制台调试半天,查看了请求的原始Response之后才发现,原来变量内容压根就都被转义掉了,能渲染出来才有鬼!

comments powered by Disqus
Published:
2014-11-12
分类:
Tag: