唠唠python(9) -- 基础概念的梳理

温顾而知新,可以为师矣。

温顾知新,可以为师。当然,我们的目标不是为师。

时常复习一下自己学习到的东西,是一种不错的习惯。就像牛的反刍,复习是对已有知识的梳理,这个过程既轻松,又能更好的巩固我们的所学,而且时常还可以关注到自己之前因匆忙而遗漏的东西。

当然,这篇文章草草看一下也是可以的,有很多东西是不需要一次掌握的。

当我们都意识不到有这么一个概念时,实际上已经掌握个七八分了,只需要再稍微整理一下思路,就可以轻松掌握了。这就是所谓的质变吧?

术语

我们首先得说说“术语”这个词。

“术语”听起来给人的第一印象是吓人,又是什么神奇的正常人看不懂的东西?

为什么会有术语这种神奇的东西呢?

人类的语言是很重要的,从诞生以来,它大大提高了人们沟通的效率。但是随着语言的发展,同一个词都衍生出了多种意思,而在使用不当的情况下,语言恰恰又阻碍了人们的沟通。

怎么来避免这种一词多义的情况呢,于是人们发明出了术语这么个东西。术语的特点在于,在某种特定的领域或上下文中,同一个术语是精确的,它会被多方理解成同一种意思,这样人们就达成了共识。

说白了,术语就是为了使大家沟通的过程尽可能少的产生歧义。

为了达到这种目的,术语就不得不避开我们平日经常会用到的词。这就导致术语看起来稀奇古怪,对我们不那么友好。实际上,它是为了精确的沟通而作了妥协,我们要体谅。

字符串

1
print "hello, I'm a string"

我们之前说,print可以显示“一句话”。实际上,“一句话”是非常不准确的,因为一句话有可能是我们听到的一段录音,也有可能是文章中的文字。我们可以找个更明确的词。

在计算机中,一个字母a叫做一个字符。顾名思义,就是一个符号,它代表一个字母。就像我们的羊肉串是把很多羊肉串起来,我们之前所说的“一句话”是由一个或多个字符组成的,故叫做字符串。

“a”是字符串,”abc”也是字符串。

变量

1
2
s = "hello, I'm a string"
print s

根据我们之前的解释,s类似于一个箱子,它可以把我们的字符串”hello, I’m a string”放进去,在使用的时候(如print s)再从里面取出来,这样就达到了类似箱子一样的暂存功能。

这里的箱子只是一种类比,在语言中有一个术语,叫做“变量”,这个术语听起来怪怪的,不那么直观。它实际上是从数学里借来的概念(说借并不合适,计算机本就是数学的一个分支),意思是没有固定的值,可以改变的数。到计算机里,它也可能是字符串或其它类型的东西。

这里的“变”很好理解,一个箱子可以放衣服,也可以放鞋袜。

当然,有变量就会有常量。比如我们的文具盒只能放铅笔像皮,放不下衣物。python是一门简洁的语言,在语法上是没有定义常量的,所以我们可以不需要纠结“常量”的概念,在这里提到是为了帮助我们理解“变量”这个概念。

功能

我们说洗衣机把洗衣服的很多步骤打包封装起来,只留给我们一个按钮开关。“洗衣机有洗衣服的功能”,虽然听起来有点白痴,但它确实是个事实。

功能的概念是很好好理解的。它比较接近我们的生活。

在计算机中也有一个概念,叫做“函数”,它是比功能更准确的一种描述。这也是一个从数学中借用过来的概念,它并非我们日常生活中提到的功能,而就是单指语言中所提供的对步骤的封装。

类是使用了生物学中的概念。

生物学中对生物有“界门纲目科属种”的不同精细程度的分类,如豹是猫科,豹属,再细分下来是豹种。计算机语言相对于生物学分类,它只有类的概念,但它提供了继承,即豹可以继承自动物,所以在计算机语言中同样可以提供如生物学分类中那样复杂的从属关系。

类中有类似变量和函数的概念,习惯上被称为属性和方法,以区分我们上面谈到的变量和函数。

实际上,类在计算机里并不是必要的。在一些古老的计算机语言里,甚至都不存在类这个概念。

使用类来抽象现实世界的编程方式,又叫做面向对象编程。而不使用类,仅使用函数和变量的编程方式,叫做面向过程编程。前者更接近人类的思考模式,但需要我们掌握更多的概念。

我们之前在操作表格文件时,用到过一个openpyxl包。它是一些类和函数文件的集合。

为了更精确地描述,我们有时会把包称作“库”。我们通常会把从网上找到的库叫做第三方库。

那哪些是第一方库和第二方库呢?有点意思,哈哈。

我们同样可以把单个文件作为类和函数等的集合。通常我们会把单文件的包叫做模块,比如之前的示例:

1
2
3
import os

print os.name

为了演示方便,我们使用了一个随python语言打包的模块,叫做os。它实际上就是一个模块。

碎碎念

想要装一个完美的x,熟悉并掌握这些概念是必须的。

不过不必急于求成,就算我们不掌握这些概念,对我们的学习也没有太大影响。

有很多优秀的东西,我们是可以潜移默化地吸收的。可能突然有一天,我们回头时猛然发现,以前不明不白的概念,竟然已经被我们彻底理解了。

在那之前,我们可以继续愉快地叨叨。

唠唠python(8) -- 使用脚本代替手工数据操作

这个问题已经过了很多天了,都记不太清细节了。

先捋一捋我们的需求。

需求

待处理表格

这个表格有点熟悉,好像在之前的文章里看到过。我们还是以它为例子。

我们有一张如上的物料订单表格。每两行对应一种物料,即需要的料数和订购的料数。不同的列对应不同时期需要的物料数量。

因为供应商提供给我们的物料都是以10000为单位的,所以就算我们只需要9颗料,也要订购10000颗料。也就是以10000为单位,对料数上取整。

我们看到其中有一些表格的数量是红颜色,它表示这些数据已经被手工填写了。每个单元格的料数以10000为单位上取整后,被填入它下面的一个单元格。

计算公式

我们在之前的文章里已经给出了订单数量的计算公式:(n+10000-1) / 10000 * 10000,其中n为需要的实际数量。

功能清单

我们列一下需要的功能清单:

  1. 打开或保存excel文件
  2. 读取或写入单元格

再不需要其它的功能了。

上一篇讲到的openpyxl很好地实现了我们需要的这些功能。

打开excel使用workbook = openpyxl.load_workbook("excel文件位置"),保存使用workbook.save("保存到的路径")

读取某个单元格使用print sheet["B5"].value,写入某个单元格使用sheet["B5"] = 30000。其中sheet是我们使用workbook.active获取的工作簿里的第一张表格。

几点逻辑

我们需要对上面的表格中,I3到S28的奇数行数据查看一遍,还需要根据单元格的内容和它下面一个单元格的内容来判断是否需要处理单元格数据。

挨个查看一遍我们会用到一个这样的语句:

1
2
for row in range(3, 30, 2):
处理row行数据

这条语句的意思是,row分别等于3到28(不包含28)的奇数,然后依次执行下面的语句。后面的2是说,从3开始,取相隔为2的数。

这里有一点需要注意,我们的行号是数字表示的,但是列号是字母,我们需要先把字母转换成数字,在处理每一列时,再把数字转换成字母。庆幸的是,python已为我们准备好这些了。

1
2
3
4
for row in range(3, 30, 2):
for col in range(ord("I"), ord("U")+1, 1):
col_char = chr(col)
处理(row, col_char)单元格数据

ord()是将一个字母转化为数字,chr()是将数字转化回字母。注意我们给ord(“U”)加了一个1,因为ord(“U”)是不包含U的。我们再一次与python的作者来了一场完美的合作。

判断是否需要处理单元格,我们可以使用下面这条语句:

1
2
if sheet["I3"].value is not None and sheet["I4"].value is None:
处理I3单元格

意思也很好懂,翻译过来就是,如果I3单元格不是空的,并且它下面的I4单元格是空的,我们就处理I3单元格,并将处理结果填充到I4单元格。

实现脚本

说了这么多,其实脚本已经差不多了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import openpyxl


workbook = openpyxl.load_workbook("a.xlsx")
sheet = workbook.active

def count_bill(n):
return (n+10000-1) / 10000 * 10000

for row in range(3, 30, 2):
for col in range(ord("I"), ord("U")+1, 1):
col_char = chr(col)

cell_src_idx = col_char + str(row)
cell_dst_idx = col_char + str(row + 1)

if (sheet[cell_src_idx].value is not None and
sheet[cell_dst_idx].value is None):
n = int(sheet[cell_src_idx].value)
sheet[cell_dst_idx] = count_bill(n)

sheet.save("b.xlsx")

14行代码,就实现了我们的需求。它读取当前目录下的a.xlsx文件,并根据我们之前所说的规则,处理指定范围内的表格数据,并将处理后的结果保存到一个新文件b.xlsx。

它真正将我们从枯燥无味的繁重工作中解放了出来,终于可以安心地睡个好觉了。

更好的脚本

当然,它还不够好,我们在这篇文章里不打算再把它变得更好,因为这个过程是很漫长的。但是它现在已经能帮我们节省比付出多几十倍甚至几百倍的时间。

它不够好,还能做怎么样的改进呢?

第一,里面全是代码,代码相对于人类自己的语言来说总没那么直观,毕竟我们是在迁就计算机嘛。

第二,每次执行时都要修改代码(输入输出文件名和单元格范围),十分之不友好。我们希望它可以让我们通过文件选择框选择某个文件,然后用鼠标拖选出我们要处理的范围,并询问我们要保存到哪个文件。

以上两点是显而易见的,特别是第二点。

不过最紧急的事情完成了,这个脚本已经可以帮我们节省下大量时间,以实现我们上面所说的改进了。

总结

终于把处理表格的傻逼工作交接给了计算机,可以舒一口气了。

为了不加重我们的心理负担,文章里有很多地方只是一带而过。至少对现在这个阶段来说,不需要知道的东西都是不重要的。如果哪里感到困惑,恭喜,这说明我们并不满足现状,想要一个更清晰的结果。

下一篇,我们先不急着改进上面说的两点不足,它们已经不那么紧急了,我们先来回顾一下之前提到的内容,并重新整理一下知识结构,让他们形成一个清晰的脉络,以便为以后装逼打下坚实的基础。

叶脉

碎碎念

零零散散地终于把这个脚本完成了,是时候回过头来梳理一下了。

小金约定,两天或两天以上的假期,每天一篇文章,失约100红包。本是想逼迫自己,竟然有时候写的不易乐乎,到了凌晨2、3点钟还在想读者会不会读懂这句话在说什么。

作者从读者的角度思考,读者又反过来从作者的角度思考,想想真的是一件很有趣的事情。

不得不说,写作的过程很快乐,虽然可能没有什么人看,但它至少已经帮助到了我。希望可以尽自己的一点微薄之力,让碰巧看到的人能生活得更轻松。

唠唠python(7) -- 包

这是我们打基础的最后一节。下一节,我们就来兑现之前许下的承诺,用脚本来实现表格的自动处理。

这里所说的包就是《你的背包》的包,是对背包概念的一种模拟。

这两天小郭去武汉玩,为了途中更方便一点,我就把我的背包给她用了(其实是我一直在用她的背包😁)。我担心公司有一些事务要处理,所以一直都保持着背笔记本回家的习惯。最近几天都是手拿笔记本,十分的不方便。

所以你看,包还是很重要的。

我们一般会把一些平时经常用到的物品放到书包,如果我们有多个包,则会把同类物品放到同个包。比如我们把纸笔放进背包里,只要背着这个背包出去,就可以在需要的时候写字了,而且背在肩上很方便。

python里的包也是类似的用途。

python中的包

互联网的兴起,让全球的人都可以有机会合作,大大提高了人们的工作效率。

实际上,python中的包还有另外一个异常重要的作用:复用。比如,我们之前说的表格处理的例子,世界上有很大一部分人都会用到表格处理的功能,如果大家每人做一套工具来操作表格(比如读取或写入表格中的某个单元格),成本很高,不利于人类知识的叠加。有句话叫站在巨人的肩膀上,说得很有道理。

那怎么做呢?

把我们的功能合集打个包,而且只把人们要用的东西提供出来。举个例子,我们要读取某个单元格的数据(比如我们要读取B5单元格的数据),只需要sheet["B5"].value就可以了,具体怎么获取的,交给那个精通表格文件处理的人就可以了,我们不需要理会。

背包很好理解,但python中的包听起来就有点玄乎了。python中的包到底是什么东西呢?或者说,它的外在表现是什么呢?

我们之前把洗衣服的操作封装成一台洗衣机,叫做功能。又把人的说学逗唱的功能和人的姓名等属性封装成一个人类的概念,叫做类。包其实就是一个功能和类的合集,也就是说,它里面既包含有功能,又包含有类。说白了,其实就是把我们之前所有的代码放到一块。

使用包

之前我们讨论类的时候,自己亲自写了一个类来更深入的体会类的概念。

现在我们使用包,再来亲自手写一个包?

这次我们不写了,我们之前谈到封装,其实就是把细节隐藏起来,而让一个东西更好用。比如一台洗衣机,我们只需要学会按按键就可以了。如果我们读了洗衣机的使用说明还不知道洗衣机怎么用,只能说这台洗衣机做得太烂了,或者说交互太差了。

我们直接来使用一个包体验一下:

1
2
import os
print os.name

执行上面两条命令后,会显示当前系统的名称。如在mac下,会显示”posix”。它是怎么获取的,我们并不关心,只要返回的名称正确,我们就认为,嗯,小伙子干得不错。

import意思是使用,我们请求使用os包,并要求显示我们os的name。

读取表格文件

我们已经来到了这里:读取表格文件。

只要能用语言精确表达出来的功能,大部分都很简单,而且非常有可能已被其他人实现了。表格处理就是这样。

现在有一个问题,既然这个包有可能已经被实现了,那我们怎么获取这个包呢?

我们知道肯定不止一个包可以做这种操作。一般我们会在网上搜索大量资料,然后对比选取符合我们需求的包。

假设我们已经阅读并对比了大量资料,发现了一个可以读写表格文件的库,叫做openpyxl

现在我们要准备安装这个包了。这是一个最好的时代,只要确定了使用哪个包,安装极其简单,我们甚至都不用关心去哪里下载,下载完成后要做什么,它会不会依赖其它的包,等等等等这些问题。python已为我们准备好了安装的方式。

1
$ pip install openpyxl

是的,就是这么一条简单的命令。然后稍许的等待,这个包就安装成功了。真的方便极了,美好的东西总能让我们心情愉快。我们甚至也不用关心它被安装到了哪,直接用就可以了。

想想如果我们用表格处理软件查看B5单元格的数据。先打开目标表格文件,然后翻到第一张表格(一个表格文件对应一个工作簿,里面可能有多张表格),找到B5单元格,并查看内容。

1
2
3
4
5
6
import openpyxl

workbook = openpyxl.load_workbook('a.xlsx')
sheet = workbook.active

print sheet["B5"].value

就这么简单的几行,就把B5单元格的内容显示出来了。

结语

现在我们已经可以获取表格中的某个单元格了,写入跟获取是同样简单的。下一篇我们来看如何写入,以及如何来实现我们之前的物料取整问题。

终于可以不用对着成百上千的数据心力交瘁,提心吊胆了。可以有更多时间喝咖啡了。想想好激动。

碎碎念

晚上9点30,写完了,可以去吃饭了,哈哈。

老板来一个煎饼果子,一盒水果,一个鸡腿。街边小吃让人愉悦。

想起申总的一句话:不吃街边小吃的人,不知道要丧失多少乐趣。

读《巨婴国》

读完这本书已经有两个月了。其间恰好碰上过年,我回到山东老家,冬天冰冷的雪夜里,或是乍暖还寒的清晨,看着捧在光下发黄的屏幕,别有一番滋味。

巨婴国封面

第一次看到封面上奇形怪状的小人时,以为是一本漫画书。这本书读起来很轻松,思考起来又有点沉重。

书中有很多很有意思的观点,比如谈到“普遍缺席的父亲”,“把儿子视为权利争夺战筹码的母亲”。我对这些并不感冒,我的父母给了我足够的爱与自由。

我感触最深的一点,是讲到“中国式好人”。武志红说他自己就是一个典型的“中国式好人”,可能大部分人读到都会产生共鸣吧,毕竟“中国式的好人”占了绝大多数,因为有集体主义在压着。

我们总是有意无意地被灌输集体主义的思想,一切为了集体,要牺牲小我成就大我。这其实就是在压制自己,因为自私是人的天性。

从达尔文的进化论来看,生物的进化,靠的是物竞天择,而不同物种如何竞争?

我们在初中时就已经知道了,即使是单细胞生物,也懂得“趋利避害”的道理。当然,说懂得是过分了,它们不用懂得,竞争会把基因里存在“趋利避害”特性的物种筛选出来。

我身边有很多人,包括过去的我,都不好意思为自己争取利益,唯恐被人说闲话,或者自己道德上过不去。

书中也提到一个原因,如果我们把自己的某部分毁掉,比如我们不为自己争取利益,那我们就站在了道德的制高点,“我把本属于自己的一部分都不要了,你还怎么来指责我?”但越是打压自己的天性,我们的能量就会变得越萎缩。

不自私的人还容易碰到的一个问题是,述情障碍。我们接受的教育是,个人要服从集体,这导致我们很难感受到自己的感觉。自私的人是坏的,是可耻的。所以我们拒绝自己自私的部分,我们不想让自己的那部分阴暗被别人看到,被拒绝的部分就陷入了黑暗。

“中国式好人”不懂得拒绝。一切为了他人,导致自己最后活得很累。而且情况往往是,我们一边帮助他人,一边却又在抱怨,因为我们并不是因为感觉到了他们的困难,而是集体主义教导我们要帮助他人,不然自己就太自私了。

“中国式好人”没有自己的个性。个性是对集体的背叛,在一个以集体为生活单位的环境下,被集体排斥是很恐怖的一件事情。所以很多人为了融入集体,干脆泯灭了自己的个性。

自私是个好东西。只有能充分感知到自己的感受,我们才能对别人的处境感同身受,也才能真正发自内心地想去帮助他人。

自私对公司的管理一样有很大的好处。吴军在讲到公司管理时引入了一个数学概念,我觉得很好,叫做“利益最大化函数”,即我们将公司的发展与员工的利益绑定,员工为自己获得最大利益的同时,也可以为公司做出最大的产出。但是,如果一群人都是一群集体主义下催生的僵尸,没有感情,没有创意,打一鞭子就动一动,这样的人,做出的产出能有多少?

“老油条”应该就是这么来的吧?

读《编写可读代码的艺术》

感谢公司的图书角,让我免费读到了这样一本好书。

用kindle已经有一段时间了,也陆陆续续地在上面买了一些电子书。但慢慢地发现,还是读纸质书来得舒服,翻起来也方便。当然,最好是自己买的书,可圈可点,不喜欢的书还可以拿来擦屁股。

然后我就联想到了李敖的读书秘诀,给大家摘一段体会一下:

我李敖看的书很少会忘掉,什么原因呢?方法好。什么方法?心狠手辣。剪刀美工刀全部用到,把书给分尸掉了,就是切开了。这一页我需要,这一段我需要,我把它按类别分开来。那背面有用怎么办呢?把它影印出来,或者一开始就买两本书,把两本书都切开以后整理出来,把要看的部分分类留存。结果一本书看完了,这本书也被分尸掉了。这就是我的看书方法。

虽然有点偏激,但至少说明,读书要活读,要跟书互动,产生参与感,还要温故知新,常翻常新。纸质书可以做到,电子书总归不那么方便。

《编写可读代码的艺术》,一个书名就已经道出了它接下来的目标了。谈到艺术,大多时候是从微小处着眼,细细体味,而非一个hello world那么简单。有生活,有哲学,代码也该像一件精心雕琢的艺术品。

这本书的结构已经非常清晰了,再做总结总显啰嗦。下面是我读这本书时想到的,并非笔记。

想人所想

这个标题看起来一点都不酷,但是做起来甚至会感动到我们自己。我们不想看到队友因为看不懂代码而苦恼的样子,我们的用户已经疲惫不堪,我们不想让他们再承受着蹩脚的使用体验。

写代码和写文章是极其类似的,作者要站在读者的角度思考问题。

读者读到这个词时是否会理解它,会不会产生歧义,是否需要了解某个背景,这段话是不是多余,会不会太啰嗦。

作者要思考比读者多得多的东西,因为作者这个角色本身,是包含了读者角色的双重角色。

站在再高一点,从系统用户的视角来看,这个字段的描述用户是否能看懂,是否正确理解了它的用途,有没有必要填写,用户是否可直观地了解有哪些注意事项,是否知道在做什么,当用户看到这个提示时是否知道它在说什么,blahblahblah一大坨,慢慢体会。

我们即是作者,又是读者;即是开发者,又是维护者;即是产品,又是用户。

遵循惯例

遵循惯例可以看作是“想人所想”的一部分。

每个行业都有自己的惯例。伴随着行业的发展,大家在相互配合中,整理出了一些行之有效的通用规则。

理想的情况是,这些通用规则,本行业的人,大家耳熟能详。

既然大家耳熟能详,就很少存在误解,也可能更好地被理解。这无疑对读者更友好。

当然,惯例也有可能带来相反的效果。《编写可读代码的艺术》里提到过一个例子,get_xxx()按惯例来说就该是O(1)复杂度的函数,该书作者坚信这一点,结果该函数并非O(1)函数,导致作者在优化速度时吃了点苦头。

不过就像爱情一样,惯例是有的,你坚信,准没错,至少它能保证你在80%的情况下是正确的。这很值。

讲究卫生

一张干净的脸总是讨人喜欢的。

记得小时候家里不富裕,还穿过打补丁的裤子。老师教导我们,裤子破了打补丁很正常,但即使打了补丁,也要把裤子洗干净。一个漂亮的补丁,甚至会给一条旧裤子增色,就是这样。

在我的理解里,讲究卫生意味着整洁和干净。注意变量的出场顺序,把相关的代码或变量分块,都是一些好习惯。

好的代码就是要看上去赏心悦目。

可能有的人觉得,只要让系统运行起来就万事大吉了,很明显,这是不负责任。

我维护过一些系统,杂乱且无用的旧代码像战场上的残骸,任性的空格空行天马行空放荡不羁,拷贝的痕迹随处可见。这些都会加重读者的心理负担。

当然,我们也有能力写出这样的代码,所以,可以安静下来想想,怎么对待自己。

提升技能

书中提到一点我觉得很受用:偶尔地翻一下语言内置的和常用的第三方库。

维护代码需要很大成本,甚至我们的大部分时间都是在做维护的工作。以我之前做的“代码发布系统”为例,开发用了一个月,后期的维护已经达到8个月之久,而且还会持续下去。

而大部分第三方库都已经过严格测试,而且可以节约我们的维护成本。

有的程序员喜欢重造轮子,出于学习目的或现存库不能满足需求还好,如果是为技术而技术,可要在关键时刻提醒一下自己啰。

屏蔽细节

最后一点同样重要,帮助我们的读者隐藏不必要的细节。

函数的一个好处是,它把繁杂的上下文剥离开,让理解变得简单。比如,send_mail_html(mail_address, html)这个函数可以给指定邮箱地址发送一段html类型的文本,只要函数封装地够好,用户或主调函数无需关心它的实现细节。

这也是自下而上编码的好处,上层提供了比下层更高一级的操作接口,并为用户屏蔽下层的实现细节。从这个意义上来说,编程语言也是自下而上中的一层实现,语言的细节我们无需关心,只要它封装地够好且使用无歧义。

再往大了说,一个好的团队也应该有屏蔽彼此细节的能力,团队中的成员由不同的领域专家组成,大家的职责彼此正交。这时,如果你是一个虚拟化技术的专家,而我更擅长用系统来为大家提供帮助,如果我的系统需要申请一个临时环境,调用你的一个接口,我可以获得一个可用的ip,你怎么生成这个环境我不关心,我怎么用你也不用关心。再这时,如果我们要改变容器的管理方式,这对我们的用户是透明的,也就是说,他们甚至都不知道我们在做这一操作,用户只需关心他们所应关心的内容即可。

函数的使用亦如是,彼此一体却又边界清晰。

总结

编码如是,生活亦如是。

唠唠python(6) -- 事物的边界和内在

上一篇文章,我们讲到可以将一批操作打包成一个“功能”,从而屏蔽其内部实现的细节。

这篇文章,我们将对屏蔽细节做出进一步的努力,并让计算机的世界更符合我们的直观感受。

边界的重要性

昨天走在街上,看到一只动物,长得个绵羊样,体型却小得多。想起之前看到狗伪装成熊猫的样子,我就在心想,这可能也是一只演技派超强的狗。

在平日里,我们会有意无意地对看到的事物进行分类,这有很多好处。

首先,它能减少我们大脑的记忆量,屏蔽具体事物的一些不必要的细节。比如,我们现在肚子很饿,想吃点东西。我们发现路边有一个服装店,正常的人类,不用走进去就知道,里面没有我们急需的东西:食物。

其次,分类可以帮助我们更快地认识新事物。迎面奔来一只恶狠狠地狗,经验告诉我们,狗是会咬人的。所以,我们远远地躲开了它。狗喜欢骨头,喜欢吃屎,只要是一只狗,就可以对它的这些行为做出判断。

既然分类这么重要,如何进行分类呢?

凭直觉,可以。我们长这么大,没吃过猪肉也见过猪上树,看到长得像猪,八成就是猪了。

当然,凭直觉有时不那么灵。我就没凭直觉一眼看出那只迷你小绵羊其实是一只狗,而且直觉不方便数字化,我们无法直观地向计算机描述我们的直觉。

所以,除了直觉,我们应该还有其它的分类方法。

我们可以通过事物之间可识别的独特区别来区分事物,也就是通过事物之间的边界。比如,男人有鸡鸡,而女人没有,只要检查一下一个人有没有鸡鸡,就能判断他是个男人还是女人了。只要交给计算机这条规则,计算机也能很容易地判断一个人的性别了。当然,反之亦然,我们猥琐地猜想眼前的这个男人是有鸡鸡的。

划分边界

我们平时接触最多的是人,叫做“人类”。我们就以人为例,来想一下我们“人类”的一些事情。

首先,人有名字,有出生年月,有属于一个人具体的属性。

其次,我们知道,人可以说话,可以写字,可以吃喝拉撒。我们可以把这些看作一个人所具有的“功能”。这里的功能,其实就是我们上一篇所说的“功能”。比如,吃可能是由一系列的动作组成的,把食物塞到嘴里,咀嚼,下咽,blahblah…一系列的动作,我们把它们打包成一个功能,就叫做“吃”。

再次,当我们说“人类”的时候,实际上,我们在说的是一个概念,一个通过边界区分出来的分类,而并非特指老王。而当我们讨论老王的时候,我们就有了更具体的信息,我们不仅知道他有名字,还知道他叫“老王”,而且知道他出生于1962年(这让我们觉得“老”字合情合理)。很有意思的一点是,我们之前说的吃喝拉撒,老王全会,而且做起来跟其他人差别不大(不像名字那样千差万别)。老王就是人类具体化后的一个对象。

介绍隔壁老王

现在我们知道了更多隔壁老王的秘密,下面我们来做一些更有意义的事:把老王介绍给计算机。

听起来怪怪的,我们先来做点稍微正常点的事,把老王介绍给楼上老赵先。

“老赵,早啊。这位是我的隔壁,叫老王。60年代的人,跟你差不多大,以后有人陪你下象棋了。”

嗯,这个介绍还算正常。

我们试着再迈出一步,把老王介绍给计算机。

这时,有一个问题出现了。老王是个人,但计算机并不知道人是个什么东西。首先,我们需要跟计算机解释人是什么。我们不需要严格的人的定义,而只要梳理一下人自身有哪些信息,可以做哪些事情就可以了。

人有名字,有出生年月,可以说话,可以写字,可以吃喝拉撒。

我们只要把这些规则简单地转化成代码:

1
2
3
4
5
6
7
8
9
10
11
12
class Person:               # 定义人类
name = '' # 人类有名字
birthday = '' # 而且有生日

def speak(self): # 人类会说话
pass

def write(self): # 会写字
pass

def trivia(self): # 吃喝拉撒样样精通
pass

这里有几个新东西。class 意思是“定义一个类”。pass 是为了先占个位,我们在这里还不想涉及到 说话 的细节。

我们在这里看到了上一篇讲到的“功能”,这里的功能跟上一篇的“功能”并无二致。在执行这几个功能时,会传入一个 self,我们可以先暂时不予理会。

好了,现在计算机已经知道人类是个什么东西了,可以介绍老王了。

“老王是一个出生于1962年的老头。”

1
2
3
4
5
mr_wang = Person()              # 有一个人
mr_wang.name = '老王' # 他的名字叫“老王”
mr_wang.birthday = '1962-10-13' # 出生于1962年10月17日

print mr_wang.name # 打印老王的名字

这时,计算机就知道老王是怎么一回事了。尽管有点明知顾问,我们最后还是打印了一下老王的名字,计算机显示了一个“老王”。

想获取老王的个人信息,使用这种方式就可以了:mr_wang.name,很简单,对吧?

重新理解老王

上面的解释很容易懂,但是有几一点没有说清楚。mr_wang = Person() 意思是“有一个人”?

很明显这是一个动作,而不是一种状态(有一个人)的描述。

为了更清楚地了解 mr_wang = Person() 的实际意义,我们来回想一下钥匙的制作过程:如果我们的钥匙丢了,要去重新配一把,来到配钥匙的地方,会发现老师傅那里有很多没齿的钥匙。这时,老师傅会根据我们那把正常钥匙的齿,来打磨一把新的钥匙给我们。mr_wang = Person() 就是取没齿的钥匙的过程。打磨的过程就是将一个对象的特殊性添加到这个对象中的过程。

也就是说,我们先从人这个类里生成一个新的人(没有姓名、没有生日),然后把老王的数据打磨到这个人里(具体化)。

开口说话

我们之前说人类可以说话,但上面这个例子并不能讲话。我们来修改一下代码,让老王可以自报家门:

1
2
3
4
5
6
7
8
9
class Person():
...
def speak(self):
print "Hello, I'm", self.name
...

...

mr_wang.speak()

为了方便说明,这里只显示了关键代码。我们实现了之前 speak() 功能里的 pass 部分,使用了之前使用过的 print

这里有几点要说明:

  1. self 是我们之前留下的小尾巴,现在可以清晰地看到,self 实际上是实例的别称。当我们通过 mr_wang.speak() 来使用 speak() 功能时,self 就是我们亲爱的老王。
  2. 我们发现,使用一个对象的功能,和使用一个对象属性形式一下,只是多了一个功能所特有的 ()

小结

需要说明一点的是,计算机中的类很少是用来分类的,因为大部分情况下,我们已经提前知道一个对象是人还是狗了。它主要是用来划分边界,让我们意识到,这个对象有哪些信息,可以做什么。

小结不应该太多废话,我们把之前的代码整理一下,看懂即可,下载链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 定义人类
class Person:
name = ''
birthday = ''

def speak(self):
print "Hello, I'm", self.name

def write(self):
pass

def trivia(self):
pass

# 具体化一个人,并写入老王的信息
mr_wang = Person()
mr_wang.name = '老王'
mr_wang.birthday = '1962-10-13'

# 让老王开口说话
mr_wang.speak()

尾注

嗯,到现在为止,我们已经忽略了很多不需要知道的细节。

但可能在实操的过程中,我们的好奇心没有停下来,也或者因为小手一抖敲错个空格,而让问题变得非常复杂。我的建议是,保存好有错误的代码,然后重新来过,毕竟到现在为止,长点的代码也不过20行。

错误都非常有价值,多犯不同的错误会让我们更有经验。当你完成了整篇文章的内容,就可以回过头来重新查看之前的错误了。询问旁边的人,或者借助互联网,都是不错的选择。

在完成一个重要的里程碑后,我们会重点整理一下之前最有可能产生误解的点。

最后,毫无疑问的是,如果你发现某一句话没看明白,或者实操时老出问题,那一定是我出问题了。请给我一个改正的机会。

读《废都》

这是第二次读这本小说了,算是重温。

第一次读是在高中。那时去姨家玩,进了一个房间后,发现墙角有一个书框,密密麻麻地摆着好几排的书,想是堂哥平时慢慢积攒起来的。那时我并不喜欢阅读,就随便翻着看。翻到这本书时,看着书里的情节,面红耳赤,不能自已。

那时我十七八岁,正是情窦初开的年纪。

借这本书时,刚好堂哥不在,胆战心惊,怕被姨识破。我把书借回了家,仔细研读并温习着书中描写的场景。特别是读到“□□□”时(此处并非敏感词,原书中确为“□□□”),心中充满无限的想象。那份荷尔蒙急剧上升带来的怦然心动,一颗鲜红滚烫的心仿佛要脱离这个僵硬的躯壳。

所以想象力很重要。

就像冯唐讲他们在网络资源匮乏时,一个视频一传十十传百的刻录,最后模糊不清,也依然平静不下观看时的激动。最后乃至看到阳光下树影斑驳,下面都会不自觉地硬起来。

十年前,山东,盛夏的某个夜晚,我躲在被窝里,战战兢兢地抱着一本泛黄的纸书,虔诚得像一个圣徒,有作为生命最原始地憧憬与渴望。我读得兴奋,从外面远远地望过来,千家万户独一盏灯火通明,肯定更美。

一晃十年过去了,“十年生死两茫茫”。十年有可能是我生命的八分之一,五分之一,甚至是三分之一。

一席春梦了无痕。十年后,午后饭饱,阳光正好。人懒洋洋地坐在办公室的座椅上,旁若无人地拿着kindle,总觉得少了点什么。手里的这个版本,已经没有了当初的“□□□”,也不再有当时斟字酌句的心境。

不知再过他个十年二十年后,看到远处的灯火通明,是否还能一眼看出,那个曾经亲爱的自己。为之感动,为之神伤。

上课走神

我记得之前的每一次考试,心里都会非常紧张,惟恐整个考试被自己搞砸。甚至多次被这样的梦境吓醒,梦中自己竟睁不开眼看试题。

我慢慢地在这样的紧张中,缩小自己心理的活动范围。

如今又做了这样一个梦,说不上是算昨天还是今天,这篇文章是一篇跨夜文章。

梦里还是高中时候的同桌。

老师说:“你们先随便看看书吧。”像是地理书。

说完以后,我就开始自顾自地玩了。

过了一会,老师说,现在开始考查刚才让你们背诵的内容。

what???

what

逗我,啥时候让我们背诵了,不是说就随便看看吗?

更夸张的是,竟然是所有人挨个过去,他提问题我们回答。搞得更像是面试。

我的天,这是什么情况,我再一次慌了。

我前边的几个人挨个过去,都答的不错。

到我过去背诵,他先问我:“你觉得你背诵得怎么样?”

我说:“背得不是很好,尤其是后面。”

他一下子就怒了,怒了,说:“那还学什么了,我要把你开除”。

开除?我清晰地觉得这是在跟我说工作的事。

看到他在纸上写下开除两个字,我一下子就释然了。一个感觉是,你没权利开除我;第二个感觉是,开除我也不怕,又不是什么大不了的事。

醒来后,我觉得自己很开心。多年的恐惧已经解除了。

也或许,我真的有了一定能力,可以理直气壮的说,我不怕失去。

我觉得我真正要害怕的是生命的失去。失去生命,意味着将不再有努力的机会。

而考试,或者老师布置的任务,并没有可以让我们害怕的因素。

可接受的生活环境

租住在现在的这个地方已经有3个年头了,却几乎没怎么主动打扫一下。

并非因为不是自己的房子感觉没必要,也不是因为平时工作忙没时间。就一个字,“懒”。

今天破天荒的打扫了一下厨房的卫生,大到物品的摆放,小到边边脚脚。用毛刷刷墙角也是很快乐的,不得不说,人类的这项发明非常之伟大。

说起打扫的原因,却是出奇的简单:我住的这个房子,厨房靠窗。春天来了,想打开窗子透透气,但一打开,外面的人就能看到里面的情景。想到这个,我立马就有点心虚。窗子还是要开的,怎么办呢?打扫一下卫生呗。

可不可以开窗,取决于一个条件,我管它叫做“可接受的生活环境”。即,我能不能接受把我的生活环境暴露给别人看。

说到可接受,有两种很极端的情况:

  1. 虽然我房子脏,但是给别人看一下也无妨,毕竟我是活给自己的嘛。
  2. 这么脏的房子,怎么能给别人看到,除非打扫一下。

我觉这两种情况都合情合理,所谓“知行合一”,只要自己跟“自己”想法一致,就不会在心里产生矛盾。

我选择了2,而且给了自己一个很好的理由:嗯,虽然你小子看起来挺懒的,但是并没有懒的无可救药,你还是想努力改变自己的。

“可接受的生活环境”其实是与懒惰抗争的一个好方法,有时候我们就是需要这种本无大意义的想法来刺激一下。当然,意义多大看个人。

“可接受的xxx”可以用在很多方面,比如,我们平日在家希望自己是一个什么形象呢?我们有可能希望接受的是,别人看到的我们的私生活是积极的,我们热爱生活,我们利用自己的时间学习烹饪、读书、运动、听音乐。

但是,私生活本来就是不活在别人的眼里呀,不然该活得多累。所以实际的情况可能是,我们热爱不正经的生活,我们看黄色小说、打飞机、睡懒觉、臭鞋臭袜子衣服满屋。

如果不用打开自己的生活给别人看,又觉得脏点乱点自己也可以逍遥快活,管它呢!

唠唠python(5) -- 打包操作

这篇文章依然是承接自上一篇文章,来讲一讲上一章遗留下来的问题。

上一章我们讲到int(raw_input('Tell me the quantity: '))是让计算机帮我们从用户那里获取一个数字,现在我们来看看它是如何做的。

洗衣机的智慧

对,我们先来谈一谈洗衣机。

我现在住的这个地方有点小,担心买了洗衣机更显局促,所以一直没买。但这不妨碍我们谈洗衣机的智慧。

如果现在还没有出现洗衣机这么个东西,设想我们渴望有一个机器,它可以帮我们(1)自动加水,(2)清洗衣物,(3)自动甩干,(4)自动烘干。

这么一大批动作,当然每个动作分别需要一个机器也是可以的,但是每一个动作人为介入,势必会降低我们的效率。怎么办呢?

哈哈,其实办法我们已经知道了,但还是要假装吃惊一下。啊?居然还有机器可以集成上面说到的全部功能,而且只需要在执行第一步前按一个按钮,接下来的四步自动依次完成,帮我们屏蔽了之前的细节。

这样的例子有很多,人类的智慧大大促进了人们生活水平的提高。

洗衣机智慧在计算机语言中的妙用

刚刚我们谈了洗衣机的智慧,我们再进一步来谈一下,对我们来说,它到底意味着什么。

我们知道,光洗衣服这件小事,就涉及了非常多个工序。什么倒洗衣粉、搓洗衣服、拧干、晾晒,等了个等。像我们这种追求(zhi)生活(nan)品质(ai)的人,怎么能忍受得了这么变态的工序。

福音来了,现在你眼前的这个工具,只需要把衣物放进去,按下开关。二小时后,将衣物取出放进衣柜就可以万事大吉了。

为什么这么神奇,因为洗衣机帮我们把洗衣服的细节给屏蔽掉了。它把必须由我们来做的事(打开开关)暴露给我们(看得见的开关按钮),我们只需要打开开关,二小时后,它就会把结果(衣服洗完)给我们。

计算机语言也存在着类似的思想。

我们再拿之前提到的表格处理的例子,我们希望把表格中每个数字,按照某种规则处理完后写到其下面的表格,然后将处理后的表格保存到一个新文件。

假设我们有一台表格洗衣机,来达到上面的效果。我们假设这台表格洗衣机是洗衣机厂家生产的。我们不管厂家怎么制作出来的,也不管它怎么个洗法,我们的想法是,只要给这台表格洗衣机一个实际的数据表格(相当于洗衣机的开关)就可以了。它会把处理完后的表格给我们。

表格洗衣机仍然有点抽象,但是实际上,我们在前面已经看过两个例子了。一个是raw_input(),一个是int()

raw_input()int()

现在,我们可以来讲讲之前遇到的int(raw_input('Tell me the quantity: '))了。

这一坨长长的东西,可以等价到下面两句:

1
2
quantity_str = raw_input('Tell me the quantity: ')
quantity = int(quantity_str)

我们对洗衣机说:“帮我洗一件衣服”。然后塞给它一件衣服,打开开关,它就轰隆隆地开动了。

当计算机读到quantity_str = raw_input('Tell me the quantity: ')时,它跟用户说:“Tell me the quantity: ”,并将用户输入的文字写到quantity_str

quantity_str又是啥?我们可以把它想象成一个箱子,我们把计算机读到的用户输入的文字放到这个箱子里。

注意,这条语句是从右往左读的:“计算机显示Tell me the quantity,并把用户输入的文字放到quantity_str这个箱子里”。

现在我们知道了,raw_input()就是要计算机帮我们获得一段文字。具体怎么获得,我们不管,你(计算机)自己去想。

int()也是一样的东西,它的功能是将某段文字(如"1234")转换成一个整数。int(quantity_str)即,将我们quantity_str箱子里的文字转换成一个整数,并放到一个名为quantity的箱子。

为了更直观地感觉一下,我们来自己做一台”洗衣机”。

更好的规则计算过程

之前的例子,我们写了一段代码来实现通过规则计算结果:print (int(raw_input("Tell me the quantity: ")) + (10000 - 1)) / 10000 * 10000

这一行代码有几个问题:

  1. 首先,这一句非常长,而且里面有n个(),就算我们自己,也要用手一个括弧一个括弧地匹配。
  2. 其次,里面可能有多个中间结果,但我们没法查看中间结果的值。
  3. 再次,这段代码的自述性不好。也就是说,它不能表达自己在做什么。

我们根据这三点,再利用前面提到的技术,来重新组织一下我们的语言:

1
2
3
4
5
quantity_str = raw_input("Tell me the quantity: ")
quantity = int(quantity_str)
real_quantity = (quantity + (10000 - 1)) / 10000 * 10000

print real_quantity

看上去代码量更多了,真的值得吗?

审视一下我们上面提到的三个问题,新的代码每行不再有大量的括弧,我们可以在第二行后插入print quantity来查看quantity的内容,而且我们很清晰地看到了我们要计算机做的每一步。作为一个简单的脚本,新的代码并没有什么吸引力。但当我们考虑到我们要把它做成一个完整的系统,至少在最后的系统面前,它是值得的。

考虑当我们的规则非常复杂时,我们就会有一个更好的选择,即上面所谈的洗衣机的智慧:

1
2
3
4
5
6
7
8
def compute_real_quantity(quantity):
return (quantity + (10000 - 1)) / 10000 * 10000

quantity_str = raw_input("Tell me the quantity: ")
quantity = int(quantity_str)
real_quantity = compute_real_quantity(quantity)

print real_quantity

哇噻,更长了,哈哈。

我们在这里,定义了一个类似raw_input()的功能,叫做compute_real_quantity()。定义方式就像看到的一样简单。我们再来定义一个加法功能说明,如下:

1
2
3
4
5
def 功能名称(需要输入哪些变量):
return 结果

def add(m, n):
return m + n

这里有一个好处,如果compute_real_quantity()是别人写的,我们就不需要知道规则的细节了。假如这个规则是你来写,我只需要把我的待采购的数量给你,你帮我计算一下实际需要的数量就好了。具体细节,我不关心。

屏蔽细节让生活更轻松

我们之前说游戏手柄的输入也非常简单,原因就在于这方面的专家已经帮我们屏蔽了细节。获取手柄的输入,跟获取raw_input()的输入大同小异。

类似的,从一个表格文件中获取输入跟raw_input()差别不大。我们会使用这一篇文章中讲到的技术及之后的少量新技术,来从表格文件中读取表格数据,处理完后,使用类似print的功能,将处理结果输出到新文件(而不是神秘的黑色窗口)。

明天你是否会想起?

不止明天,我哪一天都不想起。睡觉。