(零)
这个暑假我跟着本校的计算机教授做研究(research),就不回中国不回杭州了。
圣路易斯的夏天超级热,这两天气温一直在九十五华氏度左右,我不禁怀念起那北纬四十度的俄亥俄清风拂过村头的清凉。
而比天气还热的,是写得正热火朝天的代码。
我放假后休息了一星期用来搬家,之后便开始朝九晚五地工作。我有一个办公室,是一个挂着我名字的小隔间;我还有一个队友,是来自俄亥俄北部的女生Kimmy。
欣妈:“啥,她是你老乡啊?”
欣欣:“…亲妈你别闹。”
我刚来上班的时候,Kimmy已经在写用户界面(user interface)了,我也见证了她把主流语言试了个遍,从php到Java再到Python。我呢,看书为主,几乎每天学一样:SAS,Java Swing,XML,等等。
自从上星期Kimmy回俄亥俄以后,继续写用户界面的重任就交到了我手里。虽然我一点也不会写Python,可教授觉得我会,那我就会一下吧。
(一)
第一天我没有看代码也没有写代码。看掉了一份两百页的教程,过了一本四百页的书。
第二天我要开始写了。头皮发麻。硬上。
(二)
我打开写一半的代码,打算先运行看结果,结果什么都没运行出来。只见屏幕中央一个四四方方的白色方块,旋转的鼠标显示系统繁忙。我跑去问教授怎么回事。
教授:“刚我也以为这个代码有问题,后来Kimmy说这个是因为导入的包比较大,第一次运行大概要加载半小时。”
我:“呃。”
原来,代码直接import加一个星号,通配符把文件夹下大大小小所有的文件通通导进了程序。想想还是挺搞笑的,于是我一边笑一边开着窗口做别的事情。
笑了两小时,我笑不出来了。屏幕还是什么都没有。
教授对代码进行了一些改动,根据Qt的当前版本修改了一些写法;我也想办法精简import语句,只导入需要用到的那几个类。
周四的早上,代码终于能运行了。那是一个下拉菜单,跟着四个满是复选框的区域,每个区域还带一个“更多(more)”的按钮。非常漂亮。
(三)
我的第一个任务是,把代码改成循环(loop)的形式。
原先的代码生成的屏幕挂件(widget)是特定的,要增加新的挂件需要复制粘贴已有的代码。在研究的第一阶段,只能大致估计研究需要四个分类,每个分类下都要显示一个复选框(multiple choice group box)。原代码于是定义了box1、box2、box3、box4四个变量,所有与之相关的内容也都是这样命名的。
我于是定义了一个叫numOfCat的变量,表示一共有多少个分类,在程序的一开始赋值成等于四。又把每样四个头的东西都定义成动态的数组(list),再用大大小小各种循环来做原先要做的事情。这样,四个box开头的变量就变成了box[1]、box[2]、box[3]、box[4]。只要修改numOfCat的值,就能很方便地增加或减少屏幕挂件的个数。
然后我开始写文件读入。我写的这个程序要读入一个类似于这样的文本文件:
<variables>
<分类 1>
第1个复选框的第1个选项的文字
第1个复选框的第2个选项的文字
第1个复选框的第3个选项的文字
</分类1>
<分类 2>
第1个复选框的第1个选项的文字
第1个复选框的第2个选项的文字
</分类2>
</variables>
读进来是什么,复选框的文字便相应地显示什么。用尖括号括起来的标签相当于分隔符,是用来隔开上下相邻的分类的,有特殊的意义。我用一个动态的二维数组来储存这些字符串,数组的第一维对应不同的分类,第二维对应每个分类下不同的选项。我还增加了各种检测,确保读进来的数据是正确的格式,找不到文件的时候会给出警告。
写完这个,我发现numOfCat也可以不需要手动指定了,程序读出几个标签numOfCat就是几。好处是,不用修改程序源码,不用担心索引越界。
以上这些都不难,充其量帮我熟悉了Python的基本语法。接下来的几样才叫头疼。
(四)
关于用户界面,据说Python自带的包非常弱,所以Kimmy用的是基于Qt5的guidata。这个东西虽然很高级,用起来也很坑,网上能搜到的相关资料并不多,主要得自己去开发者文档里找调用它的写法。问题是,开发者文档都很难找,要很认真地找才能在谷歌的某个角落翻出来。而且,这个文档写得非常简略,还不如直接看源码。
靠着Qt的文档和guidata的源码,我着手对界面的大改造。
教授说,原代码生成的四个复选框,连着四个滚动条,看着不够清晰方便,最好能改成只有一个滚动条。
我定睛一看,原代码的界面的结构是这样的:主窗口绑定了一个splitter类的容器,里面装着四个复选框,由splitter来智能调整大小;滚动条和复选框是一对一绑定的,显然滚动条不能直接绑定多个复选框。
在那个周五的下午,我喝牛奶的时候灵光一现,想出了小splitter嵌套进主splitter的方法。我看到有个网友一个水平方向的splitter里嵌了好两个竖直方向的splitter,我觉得他嵌这么多都可行,那我嵌一个总没问题吧。
然后,我就去吃韩国烤肉了。
星期一回办公室,我成功地把四个滚动条变成了一个。小splitter绑定一个滚动条,再把四个复选框都丢进这个小splitter。即使两个splitter是同方向的,它们还是可以自动调整到合适的大小。主splitter的其它挂件也不会受到滚动条的影响。
超开心。
(五)
我从教授那里得到一本Python的书,一千多页,最后一章讲的是元类(metaclass)。书里说,百分之九十九的人用不到它。我要加一句,剩下那百分之一,用到的都哭瞎了。
我就是那百分之一。
有个问题从我接手代码的时候就产生了。guidata如果要生成一个复选框,需要传入一个参数来指定复选框里的内容。反人类的是,这个参数是一个继承DataSet的类(class),而不是这个类的实例(instance)。
原代码定义了四个类,又写了四行语句来生成四个复选框。我在改写成循环时,暂时简化为传入同一个类。四个复选框显示的文字一模一样,而我回头修改才发现这看似容易的最后一步,没有我想象的那么简单。
有多少个复选框,就需要多少个对应的类。所以,在分类的数量numOfCat变成动态的那一刻起,我没法提前预知类的数量,也就必须动态生成新的类。注意是类本身,而不是同一个类的实例。这就是为什么我尝试给这个类传入额外参数、试图指定复选框内容的时候,会得到一堆运行错误。我需要用新的知识,来解决这个难题。
我一时找不到答案,于是漫无目的地翻着教授那本书的目录。
由于中文相关资料比较少,我搜索和使用的所有资料都是英文的,包括那本书。当我看到元类(metaclass)那一章,标题的“meta”让我眼前一亮。这个英文词根的有“超越”的意思,比如“metamorphic”是“形而上的”。“metaclass”顾名思义,大概是超越了类本身的限制,凌驾于类原本框架的高级类。
我看到章节开头那句警告:“如果你在思考你到底需不需要元类,那就别用了,因为你正在思考的行为说明你其实并不需要它。”我心想,我思考是因为我第一次见到这玩意儿。当我看到元类的适用范围包括“根据用户输入,动态生成一些类”的时候,我几乎可以确定,我需要的就是它。
我觉得我是史上最暴力的程序员了,在学习Python的第六天,用元类强行造出一堆类,再用类里的一堆字符串数组造复选框。难题解决。
(六)
我写代码一直注重可扩展性。早年编写游戏的经历告诉我,如果想要在日后增加新功能时少费周折,那么写第一个版本时便要将可扩展性铭记于心。
我定义numOfCat的初衷便是如此。现在有四个分类不代表以后真正开始处理数据时也是四个分类,实验室的另一头,两个男生还在不断整理出新的数据文件,我手上的这些信息随时可能变化。我改写后的程序,只要输入文件(input)是正确的格式,无需我改动一丝一毫就能读入包含任意数量分类的文件。
除此之外,我还做了其它一些改动。我把读入文件的几十行语句抽离成一个函数,只要把文件名传入这个函数,它就会返回一个带着所有复选框选项文字的二维数组,我的程序可以在未来干净地读入多个文件。甚至,在教授说每个选项都有两个别名时,我轻松地给数组增加了一个维度,只修改这个函数的内容,便达到了想要的效果。
我还把那几个复选框装进一个我自己写的继承滚动条区域(ScrollArea)的类,进一步优化了代码结构。
(七)
一星期过去了,程序最重要的功能却没有解决。每天,我绞尽脑汁,却还没有发现可行的方法。
这个功能是什么,我又将会如何解决呢?等待我的下一个挑战是什么?除了研究,我空余时间又在忙些什么?
请待下回分解。
(溜走)
牛b
努力了 看不懂哈哈哈哈
哈哈哈,努力的看完了
py写UI?不!我觉得我脑壳会痛,爪子也会痛!PHP来吧!
做码农不容易啊
高效率
用python写UI真是作了个大死啊lol (咦有阿狸表情包好评!){smile:12}
挺厉害,这学习速度。
学习效率挺高的,佩服。
偶然看见,不错!
刚刚在同事面前介绍欣欣做的游戏来着,就进来了
@raintrue:好开心^_^
赶紧进屋,给你搬个小板凳
码农~~~ 好辛苦的说