Tuesday, October 27, 2009

wxPython & py2exe & Python多线程编程

这两天一直在帮别人弄个小程序, 一个用于辩论赛的计时器. 以前有人做过一个, 但现在规则改了, 估计写程序那人也找不到了, 就打算重新做一个. 开始看了一下程序, 觉得功能挺简单的, 就答应了下来, 事后我还很纳闷地跟小兰同学说我怎么就把这么一个活给接下来了, 照我以前的思维, 这种Window$下的程序我应该直接推掉才是.

不过也管不了那么多了, 既然答应了下来, 就得按照我的风格完成. 首先是GUI库的选择, 他们想要的是那种不用安装就能直接运行的轻量级程序. 这样就最先把C#给淘汰了, 那庞大的.NET Framework是肯定不行的. 其实做这种Win下的小型程序最好的选择就是VC++了, Window$系统对于MFC良好的支持, 使得程序编好之后就可以到处运行. 不过MFC自从大二上学期学过之后, 我就再也没有碰过, 况且我的机器上连VS都没有... 我最想用的其实是Python的GUI库, 主要是我最近也在学这东西, 可以练练手. 如果不用微软的GUI库, 那剩下的选择就主要有wxWidgetsGTK+QtTk这四种了, 它们都是跨平台的, 并且都提供有Python的实现接口. 其中wxWidgets不同于其它三种, 它编写出来的程序具有系统的原生界面 (look and feel native), 而其它的则是在所有系统下都一个样. 于是我选定了wxWidgets作为GUI库, 对应的Python实现是wxPython. 但对于Python这种解释型语言来说最大的限制就在于目标机器上必须也有对应的解释器才行, 这样就跟C#一样了. 不过通过我在网上的进一步了解, 发现了一个叫做py2exe的东西, 它的目的主要就是将你的Python程序打包, 使得在没有安装Python的机器上也能运行. 这不正是我想要的结果吗?! 有了上面的准备, 更让我确定了要用Python去开发的决心.

学习wxPython可以从"How to Learn wxPython"开始, 同时wxPyWiki也有一定的参考价值. 然后就是不断地翻API了, wxPython官方提供的那个Python接口的API我觉得基本没有用处, 虽然全部都是Python的代码, 但由于对于接口的参数没有一点解释, 导致你看了也是白看, 只是知道了这个方法有什么参数而已. 还是看wxWidgets的文档好点, 即使里面是以C++作为实例, 其实很容易转换到Python的. wxWidgets也有类似于GTK+中的Glade的可视化界面绘制工具, 比如wxGlade (这个很明显是模仿的Glade), wxDesigner (这是收费的软件).

在编写过程中还遇到一个小意外, 因为我一直认为这个程序技术上没有什么难点, 只是图形界面我是第一次接触, 可能要花些时间. 结果在第一天就发现这个程序非得用多线程不可... 并且是至少3个线程一起跑. 这就麻烦了, 我根本就没有学过Python的多线程. 怀着搜一搜就会有结果的心态, 找到了Python的threading模块. 以前觉得Python官方的文档挺不错的, 每一个模块写得清清楚楚, 中间还穿插一些示例代码, 让人一下就知道该怎么用了. 但似乎这个threading是个例外, 不仅文字解释模模糊糊, 唯一的几小段示例代码也让人看不懂. 看来这次是碰到硬骨头了, 没办法, 当初自信满满地答应别人一定能在期限之内做完, 硬骨头也得使劲啃. 通常是通过继承threading模块的Thread类来构造线程, 同时你可能还需要重载基类的run()方法. 这些都还比较容易理解, 但我还需要让一个线程中途暂停下来, 等到主进程来激活它. 这个"暂停线程"就把我搞得有点恼火了, 开始一直不能理解为什么其中提供的wait()方法是不带参数的, 没有参数怎么知道该让哪个线程等待呢? 在看过几个例子和自己试验了几次之后, 终于发现wait()是从属于每一个Thread对象的, 看来面向对象的思想还是没有学好... 于是要让一个线程暂停就变得异常简单了:
import threading

class MyThread(threading.Thread):
    ...

    def run(self):
        ...
        self.event = threading.Event()
        self.event.wait()
这里使用的是Event类中的wait()方法. 激活的操作在主进程中进行:
self.thread.event.set()
但Python中不提供彻底终止线程的操作, 每一个线程必须正常执行完之后才能退出. 可以通过设置Thread对象的daemon属性值为True, 使得子线程可以在主进程退出的时候一同结束. 不过这样还是不够"干净", 在主线程没有结束之前, 那些子线程是一直都在运行的, 虽然表面上看不出来, 会白白浪费系统的资源. 于是可以采用让子线程在某种条件下自己结束自己, 具体内容请参考: Python 不支持杀死子线程.

程序写好之后就还剩下打包, 最开始我其实不是用的py2exe, 而是Pyinstaller. 原因在于Pyinstaller可以只生成一个exe文件, 这样程序看起来就会很简洁了. 但用Pyinstaller试过几次之后程序都莫名其妙地无法运行, 只好转战py2exe. 相比于Pyinstaller, py2exe提供了更灵活的配置机制, 但最后生成的文件夹里会包含很多dll和pyd文件, 看着稍微有点乱. 使用py2exe主要是setup.py的编写, 调用的是distutils.core标准库中的setup()函数. setup()函数的参数可以在这里查到, 同时py2exe也附加了一些参数, 其它信息可以参考General Tips and Tricks, 比如怎样自定义程序的图标. 不过故事总会出现波折, 就像公主虽然肯定是和王子在一起, 但也不排除那个王子可能一辈子都是青蛙. 我用py2exe生成的exe文件在没有安装Python的机器上再次出现错误, 在看了"尽量别使用 Py2exe for Python 2.6"这篇文章之后, 才知道是Python 2.6在作怪. 没办法, 为了生成一个通用的exe程序, 只有再去下载Python 2.5, 之后生成的程序就一切OK了, 同时记得把一些py2exe没有复制的dll文件拷过来.

经过这次体验, 深切体会到Window$下的快速GUI开发, 还得靠VS, 拖一拖就都搞定了, 怎一个爽字了得. py2exe虽然也很方便, 但还是尽量在机器上装上Python好点, 毕竟py2exe也不是万能的, 也许会发生某些无法预期的错误. 这个小程序的源代码已经放到了我的Google Code上.

P.S. 在下载Python 2.5的时候, 发现Python官方下载点已经被墙, 只好去其它地方寻觅, #fuckgfw

No comments:

Post a Comment