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

Tuesday, October 20, 2009

川岛饼干

今天我买了一袋叫"川岛"的饼干, 不知道有没有"津实"的呢? 写这篇博客纯粹是在君文同学的怂恿与鼓励下, 主要是今天一连看了几篇他写的, 有点心神向往, XD

在写下这些文字的时候我的肚子依然处于亚健康状态, 主要原因是今天和某同学闹了别扭, 而不想去食堂吃饭, 不然我也不会遇见"川岛". 觉得好像赌气绝食这种事情只有女人才干, 不过我也就今天试试, 明天是肯定会去食堂的, 否则我的肚子也要抗议了.

最近像预料之中的一样很忙, 白天忙着上课、看书, 晚上忙着写程序、准备讲座. 讲座的内容已经基本定型了, 不知道到时候那些大一的有没有兴趣, 听取了joy与Ray的建议, 适当地加入了一点实际的演示, 提问也相对简单, 接近于计算机常识. 这周主要是把幻灯片弄出来, 中途肯定会去搜集很多素材, 然后下周开始练习演讲. 这是我最担心的一部分, 演示那些什么的我还没什么, 那些东西驾轻就熟, 闭着眼睛都能搞定, 可演讲这东西就有点麻烦了, 这是同时考验我的口才与表演能力的时候. 我得承认我的口才不怎么样, 属于那种在网上可以跟别人说得天花乱坠, 但见了面就不怎么开口的人. 除非我那天晚上说兴奋了, 一般我说兴奋了就跟网上一样能吹了, 希望到时候不要不在状态, 但必要的练习还是需要的.

对了, 前几天"美剧达人"君文同学给我推荐了最新的美剧《未来闪影》(FlashForward), 然后就和TBBT一起下了下来, 看了第一集就被剧情吸引注了, 按君文同学的话来说就是"很有创意". 接着我本着"好东西大家分享, 有困难自己憋着"的精神把这部美剧又介绍给了鸡飞飞同学, 今天刚用QQ邮箱传给他了, 明天问他的观后感.

好久没这么痛快地书写了, 看看自己的博客, 满篇尽是技术文章, "不明真相的群众"看了还以为到了什么鬼地方呢. 我已经决定以后多写些生活上的事情了, 然后还要把君文同学拉来当观众, 呵呵~

Monday, October 19, 2009

Beamer初学小记

Beamer是专业的幻灯片制作工具, 基于LaTeX, 因此学习Beamer你需要首先具备一点LaTeX的知识. 最近要弄一个幻灯片, 遂折腾之, 小记一下.

Beamer其实就是LaTeX的一个文档类 (documentclass), 和普通LaTeX文件一样, 最需要折腾的就是导言区 (preamble), 不过对于中文用户来说, 还有一个额外的东西需要考虑: 怎样才能输出中文. CCT、CJK和xeCJK都行, 不过我们现在有了更方便的选择: CTeXKit, 关于CTeXKit的详细介绍, 可以参考我以前的文章[CTeXKit: 尽情享受中文排版]. 由于Beamer已经占用了文档类, 所以就只能用直接调用宏包的方式了.

建议先从别人的模板开始练习, 这里是Beamer官方提供的几个模板. 然后参考官方文档学习, 我觉得那个文档很不错, 看着很有意思, 其中用一个生动的例子把Beamer的一些要点都介绍了. Beamer不同于普通LaTeX文档的一点就是它可以设置主题, 访问这个网站, 上面有Beamer所有默认主题 (theme) 和颜色主题 (colortheme) 的截图, 方便大家的选择, 另外网上也有很多非官方的主题.

我在使用的时候还遇到一个小问题, 由于我用的是Emacs + AUCTeX, 在每次打开Beamer文件时, AUCTeX会很"贴心"地根据inputenc宏包的编码设置自动改变Emacs的编码, 而我一开始用的是Beamer的官方模板, 其中inputenc的编码配置为latin1, 直接导致我每次重新打开Emacs的时候都会发现其中的中文变成了火星文... 为了解决这个问题, 在网上找了很久, 最后才想到老外可能和天朝人民有点不同, 于是发现刚才说的问题.

相对于广泛使用的PowerPoint, Beamer显得朴素很多, 没有绚丽的动画, 没有五彩的背景, 但Beamer想要表达的是一段演讲的精彩不在于使用的幻灯片有多么得华丽, 幻灯片只是一个用来展示的辅助工具而已, 一切还在于演讲的人, 正如和菜头所说"PPT的流行意味着两件事:一、人们的听力理解能力下降了。二、人们的口头表达能力下降了。"

附上我的Beamer模板:
\documentclass[utf8]{beamer}    % Be care of 'utf8', change it depend on your system.

\mode<presentation>
{
  \usetheme{Darmstadt}
  \usecolortheme{seahorse}
  \setbeamercovered{transparent}
}

\usepackage[english]{babel}
\usepackage{graphicx}
\usepackage{amsmath}
\usepackage{amssymb}

\usepackage{ctex}

% Title Page
\title{\textbf{For Alist}}
% \subtitle{\textbf{}}
\author{Dreamseeker \\ \texttt{gaochangjian@gmail.com}}
\institute{反GFW小组}
\date{2007年12月1日}

% This is only inserted into the PDF information catalog.
% Can be left out. 
\subject{Talks}

% Logo
\pgfdeclareimage[height=1cm]{logo}{pic/logo.jpg}
\logo{\pgfuseimage{logo}}

% For LaN
\newcommand{\LaN}{L{\scriptsize\hspace{-0.47em}\raisebox{0.23em}{A}}\hspace{-0.1em}N}

% Delete this, if you do not want the table of contents
% to pop up at the beginning of each section and subsection:
\AtBeginSection[]
{
  \begin{frame}<beamer>{Outline}
    \tableofcontents[currentsection]
  \end{frame}
}
% \AtBeginSubsection[]
% {
%   \begin{frame}<beamer>{Outline}
%     \tableofcontents[currentsection,currentsubsection]
%   \end{frame}
% }

% % If you wish to uncover everything in a step-wise fashion,
% % uncomment the following command: 
% \beamerdefaultoverlayspecification{<+->}

\begin{document}

\begin{frame}
  \titlepage
\end{frame}

\begin{frame}{Outline}
  \tableofcontents
\end{frame}

\end{document}

Sunday, October 4, 2009

DEB打包指南 (仅针对解释型语言)

这几天一直在研究DEB打包的事情, 搞得有点头大, 这里总结下具体的过程. 我主要参考了PackagingGuide/Complete - Ubuntu Wiki, Debian New Maintainers' Guide这两篇文档, 另外由于我是打包的Python代码, 所以还参考了Ubuntu: Making a .deb package out of a python program, 下面的内容仅针对类似于Python这样的非编译型语言, 而C/C++之类的打包情况可能要复杂一点.

DEB打包的过程就是一个严格按照标准进行的过程. 每一步都有相应的标准, 所以一定要熟悉其中的规则.

  1. 建立一个新的文件夹, 文件夹的命名规则为: <packagename>-<version>, 然后把所有安装时需要的东西都拷进去, 比如: 源代码、图标、文档、版权信息、ChangeLog等. 最后把这个文件夹打包成.tar.gz格式的压缩包, 与该文件夹放在同一级目录下. 这里假设我建立的文件夹叫做alist-0.6.12, 压缩包叫alist-0.6.12.tar.gz, 后面都遵照这里的约定.

  2. 进入刚才新建的文件夹, 执行以下命令:
    ~/alist/alist-0.6.12$ dh_make -e your.maintainer@address -f ../alist-0.6.12.tar.gz -c gpl
    
    这里主要是对打包进行初始化, "-e"选项后跟的是维护这个DEB包的人的Email, "-f"则是指向了上一步建立的压缩包, "-c"指明你使用的证书. Ubuntu用户如果没有dh_make(8)命令, 请安装dh-make软件包.

    成功执行后将会在上一级目录中生成一个.orig.tar.gz结尾的文件, 比如这里是alist_0.6.12.orig.tar.gz, 这个压缩包里的内容和alist-0.6.12.tar.gz是一样的. 还会在当前目录下建立一个叫做"debian"的文件夹, 这里面包含的文件就是打包最关键的地方了.

    请一定注意, dh_make(8)只能执行一次, 如果你执行完一次之后发现有错误, 那就要重新回到第一步了, 切记不要重复执行dh_make(8), 否则可能发生无法预料的错误.

  3. 现在进入刚才自动建立的"debian"文件夹, 你应该会看到很多文件, 正如前面所说, 这是打包过程中最关键的一步.

    千万别被其中繁杂的文件吓着了, 其实很多都是用不上的, 根据你的实际需要, 保留一部分就行了. 最先需要筛选的就是那些以.ex和.EX结尾的文件, 这都是些示例 (example) 文件, 对于大部分人来说, 可以直接删除, 但是如果你需要用到man page、自启动脚本等其它的一些功能还是可以参考一下的. 我这里就是把这些文件都删除了.

    现在看起来是不是清爽多了? 接下来是"docs"和"README.Debian"这两个文件. "docs"主要用于告诉dh_installdocs(1)命令哪些文件需要放到/usr/share/doc/[packagename]下, 而"README.Debian"则说明了原始文件和打包后的文件有哪些差别, 如果你都不需要用到, 那这两个文件也可以直接删除.

    "copyright"文件很容易理解, 默认生成的文件中会有一些提示信息, 你照着填写就行了. 或者你可以选择不按照它的写法, 采用自己的版权信息文件.

    上面介绍的文件都是可有可无的, 剩下的则很重要了. 先来看看"changelog"文件, 下面是原始内容:
    alist (0.6.12-1) unstable; urgency=low
    
      * Initial release (Closes: #nnnn)  <nnnn is the bug number of your ITP>
    
     -- xiaogaozi <gaochangjian@gmail.com>  Thu, 01 Oct 2009 22:35:45 +0800
    
    我们从第一行开始看起, 首先是软件包名, 这个没什么问题, 接着括号里的是版本号: 0.6.12-1, 最后为什么多了一个"1"呢? 这是用来和原始版本号区分的, "1"代表包维护者的修改次数, 比如源文件没有变化, 仅仅是打包的时候某些地方改变了, 那就修改最后这个数字. "unstable"这部分填写适用于安装的发行版的名字, 比如Ubuntu就是jaunty、intrepid、hardy之类的. 最后的"urgency"一般保持不变.

    中间这部分就是详细写上该版本有哪些改动了, 格式同普通的ChangeLog一样, 每行的开头为两个空格 + 一个星号 + 一个空格.

    "changelog"的最后一行为包维护者的名字、Email和修改日期. 这里需要注意的是修改日期的格式必须为RFC822标准所规定的, 也就是date -R输出的格式. 可这个日期的格式书写起来着实复杂, 所以这里推荐使用dch(1)命令来编辑"changelog"文件, 它不仅会自动修改日期, 还会自动帮你加上星号. Ubuntu用户可以安装devscripts软件包来得到dch(1), devscripts软件包还包含了其它很多有用的工具.

    接着是"control"文件. 下面是原始内容:
    Source: alist
    Section: unknown
    Priority: extra
    Maintainer: xiaogaozi <gaochangjian@gmail.com>
    Build-Depends: debhelper (>= 7)
    Standards-Version: 3.8.0
    Homepage: <insert the upstream URL, if relevant>
    
    Package: alist
    Architecture: any
    Depends: ${shlibs:Depends}, ${misc:Depends}
    Description: <insert up to 60 chars description>
     <insert long description, indented with spaces>
    
    这里面包含的内容就是我们平常所看到的DEB包的简介了. 每一项的具体解释请看这里, 这里简单介绍下. "Section"为软件包的类别, 普通的应用程序可以填写"utils". "Architecture"对于Python这种平台独立的解释型语言可以填写"all", 而编译型的则需要具体一点.

    "rules"文件就是一个Makefile, 安装的时候全靠这个了. 根据实际情况修改"binary-arch"项, 把不需要的功能都注释掉. 其中用到了很多以"dh_"开头的命令, 这些都在debhelper软件包中, 你可以查看man page来了解命令的作用. 对于脚本语言, 因为不需要编译, 所以必须把包含"$(MAKE)"的行都注释掉. 下面重点是编辑"install"项, 因为刚才注释掉了"$(MAKE)", 所以现在必须手动建立一个目录用来存放安装后的文件, 在"$(MAKE)"下一行写上:
    mkdir -p $(CURDIR)/debian/[packagename]
    
    记得将"[packagename]"替换成你对应的名字, "$(CURDIR)"指代的是"debian"的上一级目录, 这里即是"alist-0.6.12"目录. 剩下的就看你的具体情况了, 比如你需要把源文件都放到/usr/share/[packagename]下, 就用cp写一行命令, 需要在菜单里放置一个图标, 就把[packagename].desktop文件放到/usr/share/applications下.

    现在还剩下两个文件: "dirs"和"compat". "dirs"填写的是那些make install时没有建立的目录, "compat"则是debhelper软件包的版本号, 一般不需要修改.

    看到这里, 关于"debian"目录下的文件已经全部了解了, 下面来完成打包的最后一步.

  4. 从"debian"目录中回到上一级目录, 执行以下命令:
    ~/alist/alist-0.6.12$ debuild -k8A94AB78
    
    上面的"-k"选项后跟的是你的PGP Public ID, 关于PGP的内容, 我会在下一篇日志中介绍. 执行完以后, 你就得到了梦寐以求的DEB包啦! 赶快试试安装的效果吧~

打包DEB的过程还是有点复杂的, 尤其是第一次接触, 会遇到很多陌生的知识. 相对来说, Arch的AUR就显得方便多了.