夜里用外挂容易被查,因此想让菜熟的时间集中到白天,每次都要口算,麻烦,正好刚刚学了wxPython,一时手痒,来自己写个吧。虽说纯属自娱自乐,但也学到不少东西。现记于下。
1. 关于Python的GUI设计
一直以来都没有一个足够好的Killer IDE,这也怪不得,没有一个足够强大的后台,要想做成重量级的IDE谈何容易(要是Google拉一把就好了,可惜Google出了自己的语言Go)。
试过无数设计器(wxGlade、wxFormBuilder、FarPy...),最终还是觉得Boa Constructor不错,虽然它的界面不习惯(P.S.一定不要最大化运行Boa就行了,当然手动双击是没这问题的,但是用一些快捷管理工具就难说了 。还有就是F9直接运行整个项目,挺方便 );虽然它中文支持得不好;虽然它还是07年更新过。。。
但这已经是这些当中最好用的了。
针对以上的不足,我只好增加“人手”帮忙了:
用Boa构建纯GUI,几乎没有任何业务逻辑。另外在Boa生成的文件中_init_ctrls 里面的代码一定别去碰它 ,否则会造成Frame Designer无法加载,而这就是Boa最有价值的所在了。但Boa没有什么代码提示(可以在Explorer->Preferences->key bindings下面设置),编辑功能也比下面两者弱不少,就只用它来做GUI了。(P.S.一定要用源码而不是二进制,可以较好地支持Unicode )
业务逻辑的代码用PyDev,这是个非常优秀的IDE,基于Eclipse,此次我也学习到了它更多的用法。(如Edit->Set Encoding、在Src上右键Refresh等等)
修改Boa生成的Python代码我用PyScripter,原因有三:没找到Eclipse怎么加入它(太笨,后来摸出来是Src上右键Refresh)、修改编码格式(现在知道可以用Eclipse的Set Encoding)、对wxPython的函数提示支持比Eclipse好(这一点我目前还没找到好的解决方法,P.S.手动加入模块路径即可 )。再说PyScripter还有个比IDLE好用得多的命令行工具。另外用它对一些功能做做测试也不错(P.S. Pydev也可以,锁定一个Console为Python Console就行了 )。
三者配合,再加上wxPython的Demonstration、Python的官方文档,以及万能的Google,开发起来还算不错了。
2. 关于Python语言学到的
①将GUI与业务逻辑几乎(大概90%)完全分开,从中体会到了这种设计对于测试的方便性、业务与GUI分离带来的代码清晰。
②类的静态方法:
@staticmethod def GetCurrentTime(): pass
③用datetime对时间进行方便的加减:
import datetime #过ahour小时aminute分钟之后是什么时间 dt = datetime.datetime(year=2009, month=11, day=11, hour=ahour, minute=aminute) delta = datetime.timedelta(hours = bhour) dt += delta print dt.strftime('%H:%M')
另外CSDN的一个贴子中说有parse,什么时候试试。
④在wxPython中使用多个定时器
def InitData(self): #所有的定时器都绑定到同一个函数 self.Bind(wx.EVT_TIMER, self.OnTimer) #最后一个参数ID就是用来区分不同的定时器的 self.tm1 = wx.Timer(self, 1) self.tm1.Start(1000) self.tm2 = wx.Timer(self, 2) self.tm2.Start(1000) def OnTimer(self, evt): id = evt.GetId() if id == 1: pass elif id == 2: pass
(P.S.后来在《wxPython in Action 》的最后一章看到另一个方法: )
timer1 = wx.Timer(self) timer2 = wx.Timer(self) self.Bind(wx.EVT_TIMER, self.OnTimer1Event, timer1) self.Bind(wx.EVT_TIMER, self.OnTimer2Event, timer2)
⑤使用多线程程序
后来我又试验用多线程来模拟多个定时器的办法,尽管没这必要,但学到了东西。
def InitData(self): self.timeAt.SetValue('10:30') #use thread to set timer to refresh current time threadGetTime = threading.Thread(target=self.TimerGetTime) #设置daemon属性很重要,因此如果不设置dameon为Ture的话, #当主进程结束时,整个程序仍然要等每个线程都结束了才能结 #束,而这里线程中的函数都是无限循环,所以只有等到对窗口部件 #的操作失败才会退出,因此程序结束后总要等一会儿 #而daemon为True以后,主进程结束时会立即强制结束所有线程 threadGetTime.daemon = True threadGetTime.start() #set timer to update crop time threadUpdate = threading.Thread(target=self.TimerUpdateCrop) threadUpdate.daemon = True threadUpdate.start() def TimerUpdateCrop(self): while True: self.FillCrop() time.sleep(60) def TimerGetTime(self): while True: self.MatureTime() time.sleep(1)
另外,IBM上的一篇文章建议在Python中用进程而非线程:
全局解释器锁 (Global Interpretor Lock) 说明 Python 解释器并不是线程安全的。当前线程必须持有全局锁,以便对 Python 对象进行安全地访问。因为只有一个线程可以获得 Python 对象/C API,所以解释器每经过 100 个字节码的指令,就有规律地释放和重新获得锁。解释器对线程切换进行检查的频率可以通过
sys.setcheckinterval()
函数来进行控制。此外,还将根据潜在的阻塞 I/O 操作,释放和重新获得锁。有关更详细的信息,请参见参考资料 部分中的 Gil and Threading State 和 Threading the Global Interpreter Lock 。
需要说明的是,因为 GIL,CPU 受限的应用程序将无法从线程的使用中受益。使用 Python 时,建议使用进程,或者混合创建进程和线程。
在Python2.6中提供了multiprocessing这样一个很棒的模块,一举将线程这个软肋去掉,多进程成为强项。
The multiprocessing package offers both local and remote concurrency, effectively side-stepping the Global Interpreter Lock by using subprocesses instead of threads. Due to this, the multiprocessing module allows the programmer to fully leverage multiple processors on a given machine. It runs on both Unix and Windows.
(PS: 实际使用中发现multiprocessing在Windows平台上有不少的限制,其中一个就是要求picklable )
⑥在Python中使用了单元测试:
import FarmTimer import unittest class FarmTimerTestCase(unittest.TestCase): def SetUp(self): self.farmTimer = FarmTimer() def TearDown(self): self.farmTimer = None def testGetTimeDelta(self): self.assertEqual(datetime.timedelta(minutes=2), FarmTimer.Farm.GetTimerDelta("00:02")) if __name__ == '__main__': unittest.main()
⑦使用了sqlite,只是后来发现这么点数据实在不值得用数据库,于是直接用tuple of tuple:
crops = (
#name, rank, count, (times,)
#can be added col before time
(u'牧草', 0, 1, (8,)),
(u'白萝卜', 0, 1, (10,)),
...)
小结:学程序是要靠多写的,写一个小程序就让我写到了这么多,而且还留下很多值得继续深入的话题,比如:Py2exe打包后在XP上无法正常运行、mutiprocess模块的使用等等。我一定还会回来的!