Python-Catalog Archive:

Page 1 of 212

最简单方法远程调试Python多进程子程序

Python 2.6新增的multiprocessing,即多进程,给子进程代码调试有点困难,比如python自带的pdb如果直接在子进程代码里面启动会抛出一堆异常,原因是子进程的stdin/out/err等文件都已关闭,pdb无法调用。据闻winpdb、Wing IDE的调试器能够支持这样的远程调试,但似乎过于重量级(好吧前者比后者要轻多了,但一样要wxPython的环境,再说pdb的灵活可靠它们难以比拟)。

其实只需稍作改动即可用pdb继续调试子进程的代码,思路来自这个博客:子进程的stdin/out/err关闭了,那可以自己重新按/dev/stdout的名称打开来用。当然这指*nix下,win下要麻烦一些,后面再说。

pdb支持自定义输出输入的文件,我再稍作改动,使用fifo管道(Named Pipe)来完成pdb的输出输入的重定向,这样的好处是,可以同时对父子进程调试!

multiproces_debug.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/python
 
import multiprocessing
import pdb
 
def child_process():
    print "Child-Process"
    pdb.Pdb(stdin=open('p_in', 'r+'), stdout=open('p_out', 'w+')).set_trace()
    var = "debug me!"
 
def main_process():
    print "Parent-Process"
    p = multiprocessing.Process(target = child_process)
    p.start()
    pdb.set_trace()
    var = "debug me!"
    p.join()
 
if __name__ == "__main__":
    main_process()

只需要给pdb的构造参数传入stdin/stdout的文件对象,调试过程的输出输入就自然以传入的文件为方向了。这里需要两个管道文件p_in、p_out,运行脚本之前,使用命令mkfifo p_in p_out同时建立。这还未完成,还需要个外部程序来跟管道交互:

debug_cmd.sh

1
2
3
4
5
6
7
#!/bin/bash
 
cat p_out &
while [[ 1 ]]; do
    read -e cmd
    echo $cmd>p_in
done

很简单的bash。因为fifo管道在写入端未传入数据时,读取端是阻塞的(反之亦然),所以cat的显示挂在后台,当调试的程序结束后,管道传出EOF,cat就自动退出了。

实验开始:先在一个终端运行debug_cmd.sh(其实顺序无关),其光标停在新的一行,再在另外一个终端运行multiproces_debug.py,可见到两个终端同时出现了(Pdb)的指示符,可以同时对父子进程调试了!

在Windows下使用管道就没这么方便了,因为没有实体的管道文件支持,可以考虑使用socket的类文件对象传给pdb。但这样要写的python代码就多一点,以及要另外用做个交互程序;不过依然用不了多少代码,可以写成一个模块专门用做远程调试,import即用。暂未实现,以后有空弄好放代码出来。

Tags: , , , , , , ,

暂无评论

Python, C-Python, Cython代码与GIL的交互

这篇笔记相对Python来说,有点底层,先来解释几个名词:

C-Python: 或者CPython,指C实现的Python虚拟机的基础API。最通用的Python就是是基于C实现的,它的底层API称为C-Python API,所有Python代码的最终变成这些API以及数据结构的调用,才有了Python世界的精彩;

Cython:准确说Cython是单独的一门语言,专门用来写在Python里面import用的扩展库。实际上Cython的语法基本上跟Python一致,而Cython有专门的“编译器”先将Cython代码转变成C(自动加入了一大堆的C-Python API),然后使用C编译器编译出最终的Python可调用的模块。

GIL:Global Interpreter Lock,是Python虚拟机的多线程机制的核心机制,翻译为:全局解释器锁。其实Python线程是操作系统级别的线程,在不同平台有不同的底层实现(如win下就用win32_thread, posix下就用pthread等),Python解释器为了使所有对象的操作是线程安全的,使用了一个全局锁(GIL)来同步所有的线程,所以造成“一个时刻只有一个Python线程运行”的伪线程假象。GIL是个颗粒度很大的锁,它的实现跟性能问题多年来也引起过争议,但到今天它还是经受起了考验,即使它让Python在多核平台下CPU得不到最大发挥。

GIL的作用很简单,任何一个线程除非获得锁,否则都在睡眠,而如果获得锁的线程一刻不释放锁,别的线程就永远睡眠下去。对于纯Python线程,这个问题不大,Python代码会通过解释器实时转换成微指令,而解释器给他们算着,每个线程执行了一定的指令数后就要把机会让给别的线程。这个过程中操作系统的调度作用比较微妙,不管操作系统怎么调度,即使把有锁线程挂起到后台,尝试唤醒没锁的,解释器也不给他任何执行机会,所以Python对象很安全。

所以一般来说,做纯Python的编程不需要考虑到GIL,它们是不同层面的东西,但是模块级别的C-Python、Cython等C层面的代码,跟Python虚拟机是平起平坐的,所以GIL很可能需要考虑,特别那些代码涉及IO阻塞、长时间运算、休眠等情况的时候(否则整个Python都在等这个耗时操作的返回,因为他们没获得锁,急也没办法)。

想体现这个过程,很简单,考虑下面的代码,一段纯Python和一段纯C的循环,每次print一段文字就睡眠一秒。

1
2
3
4
5
6
7
void _c_loop ( void )
{
    while(1) {
        printf("Print from C loop\n");
        sleep(1);
    }
}
1
2
3
4
def _py_loop():
    while True:
        print "Print from Python loop"
        time.sleep(1)

先不管他们是如何揉合到同一Python进程里面,两个进程分别执行了这两个函数后,他们应该以大概相互间隔着输出文字;但实际情况是,Print from Python loop这句出现了一次之后(先启动了纯Python线程,否则它连启动的机会都没),剩下的输出全都是Print from C loop,不断的输出,按Ctrl + C都没反应(因为响应信号的只有住线程,终止信号是发出了,说不定主线程也收到了,但是解释器不给机会),只好从另外的控制台kill了整个python进程。

显然问题在于,运行C那段程序的进程获取了GIL,但是这是个死循环是永远都出不去的,所以GIL永远都在这个进程手里,别的进程只能睡觉。考虑到这个C程序的情况,因为循环里面只有printf跟sleep两个操作系统的调用,完全威胁不到Python对象的安全,所以GIL完全没必要插手进来。解决办法有两种:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void _c_loop_1 ( void )
{
    Py_BEGIN_ALLOW_THREADS 
    while(1) {
        printf("Print from C loop\n");
        sleep(1);
    }
    Py_END_ALLOW_THREADS
}
 
void _c_loop_2 ( void )
{
    while(1) {
        Py_BEGIN_ALLOW_THREADS
        printf("Print from C loop\n");
        sleep(1);
        Py_END_ALLOW_THREADS
    }
}

Py_BEGIN_ALLOW_THREADS、Py_END_ALLOW_THREADS是定义在Python的C-API头文件里面的宏,需要给头文件包含上#include

两个解决办法看起来差不多,效果也差不多,但意义不大一样。

_c_loop_1是在进入永远都不会停的循环之前,把GIL交出去(还有保存自己的线程状态,但现在的例子这个没有意义),然后自己继续运行,这时候_c_loop_1是跟python虚拟机是互不相关的线程了,井水不犯河水,解释器的GIL爱给谁给谁。这时候“Python只能有一个线程运行”的传说就被破除迷信了。

_c_loop_2则是每次进行printf/sleep的调用前交出GIL,之后又申请要回来,然后循环回去马上又交出去(=。=)……

这个例子来说,当然是前者有效率了(=。=反正都是无用功,何来效率),只是为了在复杂的情况(C代码会影响Python对象,调用Python的函数等)下灵活运用。Py_BEGIN_ALLOW_THREADS、Py_END_ALLOW_THREADS其实只是两句语句的宏包含,Python手册里面有详细说明,甚至直接打开Python的include就可以看到。其中BEGIN那句调用了PyEval_SaveThread(),而打开Python源码,PyEval_SaveThread函数里面毅然有PyThread_release_lock()的调用。

上面这些,在《Python源码剖析——深度探索动态语言核心技术》一书,第15章Python多线程机制 15.4.2阻塞调度一节讲解得比较详细,书里面是直接拿出time.sleep/raw_input这些Python本身的代码来举例和分析。当然咯,本文加入了很多PT自己的发散性理解。

Cython在旁边说,怎么还没到我出场。恩,马上就来。

Cython其实是很纠结的东西,因为他用的是Python一样的语法,但经常不得不时时刻刻提醒自己,有时候是在写C,有时候在写Python(=。=)。C是强类型的,Python是弱类型的,所以写Cython的时候有时候是强类型的,有时候是弱类型的,一般变量还好,你可能会想一下类型,像我刚入门,经常把漏写参数类型,不过还好Cython的编译器是有提示的,当熟悉了规则后,用Python写C啊……通过Cython,不仅可以在Python调用C,还可以在C调用Python……Python的上天入地就靠他了。

Cython代码最终都是会编译成C的,上面例子那段py_loop,改个名字叫cy_loop吧,直接放入Cython里面编译即可,他完全会变成C,然后变得像C一样霸道,执行后拿了GIL永远不还回去。

显然,只有纯Python的代码需要在执行时候经过解释器解释的,才会“自动的”交回GIL,而Cython是编译成C的代码,执行不经过解释器,如果他不交回GIL,就拿它没办法的了。

Cython是留有GIL的交互接口,在Cython手册里面加起来才半页的说明,语焉不详地介绍了Cython下跟GIL的交互方法,如果没理解上面GIL的工作方式,像我那样把这两页翻烂了都看不懂的。像刚才的例子,cy_loop可以写成这样:

1
2
3
4
5
def cy_loop():
    with nogil:
        while True:
            printf("Print from Cython loop\n")
            sleep(1)

就是用with nogil块把循环围起来,对,就加一行。如果打开Cython转换而成的C代码,发现Cython用Py_UNBLOCK_THREADS和Py_BLOCK_THREADS把这段循环括起来了,查看Python C-API,Py_UNBLOCK_THREADS跟Py_BEGIN_ALLOW_THREADS其实是一样的,只差一个花括号。不过Cython还会盯着你在nogil块里面的代码,里面不能引用Python对象,不能调用Python的函数,要画清界线!啊我只是调用个printf跟sleep,人格保证这是C库的函数!可是这样Cython还是不让过,说在nogil里面不能调用需要gil的函数,恩,printf跟sleep是通过cdef预定义的,Cython可不知道这些函数是哪里的,就默认它们需要gil了。所以要在cdef引入的时候,在后面加上nogil的声明:

1
2
3
cdef extern from *:
    unsigned int sleep(unsigned int seconds) nogil
    int printf(char *format, ...) nogil

嗯,就这样,Cython编译器终于高兴了。

Cython里面关于GIL的另外一个接口是:

1
2
cdef void callback_func(void) with gil:
    ...

Cython会生成PyGILState_Ensure()的调用,来保证这个函数在线程获得锁的时候才运行。这个情况一般是用于C的回调函数,因为回调运行是不知道什么时候的,如果这个函数里面有对Python的引用,就需要保证获得了GIL才操作。又或者你在C代码里面又生成了新的线程,而且也会引用Python的东西(纠结……)。

本文例子代码包可这里下载,或Google Code在线查看;例子里面是分别是Python、Cython、C三个循环,因为有Cython的GIL接口,C代码里面就没有使用API的宏了(注释掉)。例子里面添加了全局变量end,主线程进入pause后按Ctrl + C触发使其为真,然后三个线程会相继退出。例子在linux下编译,如在win下编译可能要修改下C代码里面的sleep,以及signal.pause()。

例子代码运行效果:

Tags: , , , , , , ,

评论 (1)

什么是pythonic?

昨天的技术沙龙上,清风大妈Zoom.Quiet给大家提了个“很基础很基本”的问题,什么是pythonic?

自己没特地做过功课,但pythonic这个词不陌生,应该见过几次,但是要说具体是什么,确实很空白。说起python,我首先想起来的是其很让人愉悦的编码体验,一些很常用的封装让人感觉很“惊艳”,比如说,for line in open('file'):ooxxooxx,替代了C++、Java等里面的readline,更不会让人产生在C里面打开文件时候那种恐惧感;还有一些很贴心的细节就是,高度对象化,比如说gtk的TreeViewModel,可以直接送给SQL cursor的execute,直接把用户界面的数据写入到数据库里面去……因为都是list。

像这样的“愉悦感”,就是我理解的pythonic。昨天会场上有人的答案是:“简洁,优雅,高效”,获得比较多人的认可(得到了奖品带大妈签名的《可爱的Python》一本)。

回来后看了看华蟒用户组的首页,呃,很明显写着嘛:申明 pythonic == "大道至简",也八九不离十了。

这两天也是这个邮件组里面有人讨论实现小时候铅笔盒上的九九乘法表,最获得大家认可的答案是:

print "".join([('%s*%s=%s%s' % (y,x,x*y,'\n' if x==y else '\t')) for x in range(1,10) for y in range(1,10) if x >= y])

看了犯晕,这很明显跟python给人“惊艳感”的做法背道而驰的,但怎么会这么多人认可呢……同时想起咋们群主“逆”说一直没搞懂的一句算素数的python:

print reduce(lambda l,y:not 0 in map(lambda x:y % x, l) and l+[y] or l,xrange(2,1000), [] )

这叫做pythonic?既不简洁,又不优雅,更不高效,充其量作为一个脑力游戏,还不如玩玩数独!

玩小聪明一直都是天朝人的传统,人家孔乙己还会“茴”字的N个写法呢,但这类小聪明,着实应该远离python。

Tags: , ,

评论 (2)

通过python实现mutipart/form发送数据到paste.ubuntu.org.cn

paste.ubuntu.org.cn是国内很多linuxer喜爱的“在线剪贴板”,在跟网友交流时把代码、截图等发在这里,然后把网址发送给对方即可,而且对多种常见代码支持语法高亮,功能简单贴心。(不用像某网友在这个博客上篇帖子里面那样,在留言里面贴一大堆乱哄哄的代码……=。=)

虽说方便,但平时要发送文件时候还是要打开浏览器,再贴代码或者选择文件,多少有点繁琐,所以打算用python写个上传脚本,跟nautilous结合的话,上传截图就方便多了。

首先问球猫要了个ubpaste的perl的脚本,虽然我不懂perl,但发现上传部分只有10来行代码嘛……看来挺简单的,可能用urllib随便弄一下就可以了……结果发现,不行!paste.ubuntu.org.cn用的是mutipart/form协议方式的上传,而python标准库里面没有直接支持这种协议(perl却有……而且自动支持……所以几行代码搞定=。=)……

查了一下资料后自己写了个class来实现mutipart的boundary,才知道用http来发送文件,特别是上传大文件是这么麻烦的事情……不过还好,不算很复杂,但是整个脚本下来居然有150行代码了……=。=

现在还不能直接拿来当nautilous script用,因为第一个参数是读入文字而不是文件,还在犹豫用bash来重新封装(使用curl一行就搞定上面所说的上传了)……[懒ing]

Tags: , , ,

评论 (4)

错误生涯:Warning: unable to set property `editable' of type `gboolean' from value of type `gchararray'

Warning: unable to set property `editable' of type `gboolean' from value of type `gchararray'

做GTK编程的时候,使用TreeView控件时出现这个警告,也就是无法使单元格变为“editable”,原因出在这里:

column = gtk.TreeViewColumn(columName[columnNum], renderer, editable=True)

原理解TreeViewColumn的构造函数接受的参数里面可以接受设置Cellrenderer的属性,就直接给editable设True,于是就得到以上警告。

换用add_attribute、set_attributes,均是如此。

Google上搜到同样错误警告,但是他的原因是设错值类型,我明明设了True啊……

自己观察pygtk-demo的代码和手册,突然发现在构造函数里面给出的属性设置值不应该是直接的值,而是对应Model里面的相应column的值!看手册的描述:

Zero or more attribute=column pairs may be added to specify from which tree model column to retrieve the attribute value.

呃,果然是看手册时候大意了,这么多年来还是让英语介词搞的晕头转向。如果在构造函数里面设True这个值,就会被解析为1,去对应Model里面第二栏的类型,是字符串的gchararray,当然对不上了。

解决办法是renderer.set_property("editable", True),调用继承自GObject的set_property方法来设置对象属性。

Tags: , , ,

暂无评论

ibus数据库高频词错误修正脚本

使用ibus时间长了,常常突然发现有些本来常驻的首选或者常用字词突然掉到后面,甚至到了第二页,并不是被其他词挤掉,而是可能ibus的用户数据库出现错乱了。

不知道这是ibus程序的bug,还是ibus所用的SQLite数据库系统本身的问题,本来当用户输入一个拼音,ibus从用户数据库里面提出对应字的用户输入频数,决定字词的位置;如果用户第一次选择输入某个字,那么该字的记录就添加到用户数据库中,下次输入时便以此记录来提前该字的位置。理论上,在用户数据库里面一个词条的记录最多只能出现一次(多音字算多个字),然而,在实际的使用中,有时不知什么原因,某个本来常用的字被当作第一次输入再次加入到数据库当中,下次输入时,该字便作为低频字来排序,导致位置变得很后,带来不少不便。

这个Python脚本就是把这样的词条找出来,并把后来加入的记录删掉,把词条频数还原。

脚本下载:http://code.google.com/p/ptcoding/source/browse/trunk/ibus_fix
(svn目录内的ibux_db_fix.py,其他的两个是测试脚本)

程序功能:

  1. 自动备份用户词库
  2. 检出用户数据库中出现了两次,但不是多音字词的词条
  3. 将后加入的词条删除

检出错词的SQL:

SELECT * FROM py_phrase
WHERE phrase IN
(SELECT phrase
FROM py_phrase
GROUP BY phrase
HAVING COUNT(*) = 2)

尚存缺陷:

  1. 如果同一个词条的记录出现了3次或以上,程序不能鉴别(极少可能出现,可修改脚本内的SQL语句来查询出来)
  2. 如果一个字本身是多音字,其中一个音节出现了上述情况,程序不能鉴别(貌似概率也挺低的)
  3. 如果两个记录中的用户输入频数相同,两条记录都会被删掉(倒不是坏事,影响不大)

Python源码:
...
[阅读全文...]

Tags: , ,

评论 (1)

测试ibus输入法默认词库的流行词覆盖度

这些天一直在想怎么扩充ibus输入法的词库,虽然一般使用感觉还好。在网上找到sogou提供了一个“互联网词库”,里面是搜索引擎分析出来的15万多词语,本想拿来导入到ibus,先用python测试了一下有多少词语已经在ibus的默认词库中,最后发现15万流行词中只有200多不在默认词库中,ibus词库确实挺优秀。

程序输出:(测试代码见后)

seached: 157200 times. 215 phrases not in the database,
written in file 'notexist'

查看notexist文件,发现除了后半部分一大堆频度为1的成语之外,只有20多个大频率词没在默认词库:
(- -|原来连“裸体”都没有?太和谐了!建议广滇驹推荐ibus为国家首选输入法)

乾坤 3561275 N,
乾隆 3088184 N,
乾净 1533219
夥伴 1052393
瞭望 984469
宏碁 979267
乾脆 953204
乾燥 624377
清乾隆 480337
乾隆皇帝 380252 N,
阿房宫 235461
乾隆年间 214986
定乾坤 210477
乾隆帝 149133
乾坤袋 143966
著色 111072
萧乾 84647
小夥子 79076
瞭望台 71630
寒伧 50780 V,ADJ,
祼体 46797

其实ibus词库不用再怎么扩充了,呵呵,当然萌萌的草泥马、雅篾蝶、法克鱿之类的新新词汇,还得用户自己敲一下咯,或者能找到专用的神兽词库……
...
[阅读全文...]

Tags: , ,

暂无评论

PyETGO 专辑下载脚本 Python练习作品


PyETGO 0.3 更新版下载:http://code.google.com/p/ptcoding/source/browse/trunk/PyETGO

本脚本可跨平台使用,在Win下需要wget for Windows, 把wget.exe放在脚本所在目录即可。
Wget下载:http://www.interlog.com/~tcharron/wgetwin-1_5_3_1-binary.zip

感谢Twitter上的好友mengzehe关于Win下使用的测试和提醒。
感谢Ubuntu论坛上网友的测试。
v0.3
2009.03.16
    -对获取的XML列表中存在的非法字符进行过滤(解决曲名含"&"等不规则字符导致无法下载)

v0.2更新

  • 2009.03.11 
  • -为在Win下使用本脚本,全部使用Unicode的字符串来提示(0.1有部分乱码)
  • -修改写入Intro.txt文件的方法,使用writelines,会根据操作系统不同写入不同换行符,减少乱码
  • -写入文件名前过滤Win下的非法文件名字符/\:<>?|等

大概实现这样的功能:

  1. 下载http://music.etgo.cn/上的任意专辑的音乐文件
  2. 专辑的存放目录命名为“歌手名 - 专辑名”
  3. 多CD的专辑,音乐文件命名为“CD号-轨号_歌手 - 歌名”,单CD则为“轨号_歌手 - 歌名”
  4. 下载专辑的封面和封底文件cover.jpg、coverback.jpg
  5. 从页面中抽出的专辑信息和介绍文字写入Intro.txt
  6. 首次使用需要用-u和-p输入一个ETGO账户以获取Cookie,以后下载只需用-a指定一个专辑的页面
  7. 下载时可随时使用Ctrl + C中断,重新下载时自动从断点续传。

花了好几天的时间在这个脚本上,基本把Python的特性摸熟了。

ETGO是国内一个娱乐网站,有电影、Mp3等,资源不算新,格式也就192~256K那样,没太大特色,但其有自己的服务器,运营稳定,这几年来我偶尔都从那里Down些专辑,基本上浏览器的嗅探+序号批量下载就可搞定,而且速度不赖。网站的免费试听使用的是Flash的播放器,这个脚本则模拟了播放器读取列表的功能,骗回所有Mp3的原始地址,然后调用wget下载。

稍微记录下开发过程。
...
[阅读全文...]

Tags: , , ,

评论 (3)

Page 1 of 212