自学是门手艺

来源

# pseudo-code of selfteaching in Python

def teach_yourself(anything):
    while not create():
        learn()
        practice()
    return teach_yourself(another)

teach_yourself(coding)

前言

学会自学。

自学能力,对每个个体来说,是这个变化频率和变化幅度都在不断加大的时代里最具价值的能力。具备这个能力,不一定能直接增加一个人的幸福感(虽然实际上常常确实能),但它一定会缓解甚至消除一个人的焦虑 —— 若是在一个以肉眼可见的方式变化着的环境里生存,却心知肚明自己已然原地踏步许久,自己正在被这个时代甩在身后,谁能不焦虑呢?

完成这本书的内容,起码会习得一个新技能:编程。

绝大多数人,终其一生都没有自学过什么。他们也不是没学过,也不是没辛苦过,但事实却是:他们在有人教、有人带、有人逼的情况下都没真学明白那些基础知识…… 更可怕的是,他们学的那些东西中,绝大多数终其一生只有一个用处:考试。于是,考试过后,那些东西就 “考过即弃” 了…… 不得不承认,应试教育的确是磨灭自学能力的最有效方法

在随后的生活里,尽管能意识到自己应该学点什么,常有 “要是我也会这个东西就好了” 的想法,但基本上百分之百以无奈结束 —— 再也没有人教、再也没有人带、再也没有人逼…… 于是,每次 “决心重新做人” 都默默地改成 “继续做人” 而后逢年过节再次许愿 “重新做人”……

这是有趣而又尴尬的真相:

没有不学习的人。

别人无法贩卖给你焦虑,是你自己焦虑 —— 是你自己在为自己不断积累越来越多的焦虑……

自学能力强的人,并非不花钱,甚至他们花的钱可能更多。他们也花钱买书,而且买更多的书;他们也可能花钱上课,而且要上就上最好的课、最好的班;他们更经常费尽周折找到恰当的人咨询、求教、探讨 —— 所以,事实上,他们更可能花了更多的钱……

但自学能力强的人不焦虑,起码他们不会因为学习以及学习过程而焦虑 —— 这是重大差别。

而焦虑的大多数,并不是因为别人贩卖焦虑给他们,他们才 “拥有” 那些焦虑 —— 他们一直在焦虑,并且越来越焦虑……

为什么呢?总也学不会、学不好,换做是你,你不焦虑吗?!

生活质量就是这样一点一点被消磨掉的 —— 最消耗生活质量的东西,就是焦虑。

我相信,若是《自学是门手艺》这本书真的有用,它的重要用处之一就是能够缓解你的焦虑,让你明白,首先焦虑没用,其次,有办法也有途径让你摆脱过往一事无成的状况,逐步产生积累,并且逐步体会到那积累的作用,甚至最后还能感觉到更多积累带来的加速度…… 到那时候,焦虑就是 “别人的事情” 了。

自学没有什么 “秘诀”。它是一门手艺,并且,严格意义上来讲,它只是一门手艺。

手艺的特点就是无需天分。手艺的特点就是熟练程度决定一切。从这一点上来看,自学这门手艺和擀饺子皮没什么区别 —— 就那点事,刚开始谁都笨手笨脚,但熟练了之后,就那么回事…… 而已。

做什么事都有技巧,这不可否认。

自学当然也有技巧…… 不过,请做好思想准备:

这儿的空间,没什么新鲜……

一切与自学相关的技巧都是老生常谈。

人类有很多天赋。就好像我反复提到的那样,“就算不懂也会用” 是人类的特长。同样的道理,人类在这方面同样擅长:

无论自己什么样,在 “判断别人到底是不是真的很成功” 上,基本上有 99% 的把握……

所以,十岁不到的时候,绝大多数小朋友就 “看穿” 了父母,后来再 “看穿” 了老师…… 发现他们整天说的都是他们自己做不到的事情…… 于是误以为自己 “看穿” 了整个世界。

那时候小朋友们还没学、或者没学好概率这个重要知识,于是,他们并不知道那只不过是 99% 的情况,而且更不知道 “因素的重要性与它所占的比例常常全无正相关”,所以当然不知道那自己尚未见到的 1% 才可能是最重要的……

于是,99% 的小朋友们一不小心就把自己 “搭了进去”:

不仅讨厌老生常谈,而且偏要对着干,干着干着就把自己变成了另外一个属于那 99% 的另外一个老生……

这是 99% 的人终其一生的生动写照。

1% 很难吗?真的很简单,有时仅仅一条就可能奏效:

在自学这件事上,重视一切老生常谈……

如何证明你真的读过这本书?

如此看来,付费之后并不阅读,只能欺骗一个对象了:自己。跟心理学家们之前想象的不同,我认为人们通常是不会欺骗自己的,至少很难 “故意欺骗自己”。所以,对于 “买了之后坚决不读” 这个现象,我不认为 “给自己虚妄的满足感” 是最好的解释。

更朴素一点,更接近真相的解释是:

那百分之七八十的人,其实是想着给自己一个希望……

—— 等我有空了一定看。嗯。

说来好笑,其实每个人共同拥有的目标之一是这样的:

成为前百分之二十的少数人……

然而,PK 掉百分之七八十的人的方法真的很简单很简单啊:

把买来的书都真真切切地认真读过就可以了。

这实在是太简单了罢?!可是…… 我知道你刚刚那个没出息的闪念:

那我少买书甚至不买书不就可以了吗?

你自己都知道这是荒谬的,却忍不住为你的小聪明得意 —— 其实吧,幸亏有你们在,否则我们怎么混进前百分之二十呢?

PoW

比特币这个地球上第一个真正被证明为可行的区块链应用中有一个特别重要的概念,叫做 “工作证明”(Proof of Work)—— 你干活了就是干活了,你没干活就是没干活,你的工作是可被证明的……

借用这个思路,我设计了个方法,让你有办法证明自己就是看过这本书,就是读完了这本书 —— 你能向自己也向别人证明自己曾经的工作…… 是不是挺好?

证明的方法是使用 github.com 这个网站以及版本控制工具 git

具体步骤

请按照以下步骤操作:

  1. 注册 github.com 帐号 —— 无论如何你都必须有 github 账户;
  2. 使用浏览器访问 https://github.com/selfteaching/the-craft-of-selfteaching
  3. 在页面右上部找到 “Fork” 按钮,将该仓库 Fork 到你自己的账户中;
  4. 使用 git clone 命令或者使用 Desktop for Githubthe craft of selfteaching 这个你 Fork 过来的仓库克隆到本地;
  5. 按照 Jupyterlab 的安装与配置 的说明在本地搭建好 Jupyterlab —— 如果在 Jupyterlab 中浏览本书的话,其中的所有代码都是可以 “当场执行” 的,并且,你还可以直接改着玩……
  6. 在阅读过程中,可以不断通过修改文章中的代码作为练习 —— 这样做的结果就是已阅读过的文件会发生变化…… 每读完一章,甚至时时刻刻,你都可以通过 git commit 命令向你自己 Fork 过来的仓库提交变化 —— 这就是你的阅读工作证明;
  7. 仓库里有一个目录,my-notes,你可以把你在学习过程中写的笔记放在那里;
  8. 仓库里还有另外一个目录,from-readers;那是用来收集读者反馈的 —— 将来你可以写一篇《我的自学之路》,放在这个目录里,单独创建一个分支,而后提交 pull request,接受其他读者投票,若是达到一定的赞同率,那么你的文章就会被收录到主仓库中被更多人看到,激励更多的人像你一样走上自学之路……

当然,为了这么做,你还要多学一样反正你早晚都必须学会的东西,Git —— 请参阅附录《Git 入门》。

时间就是这样,我们没办法糊弄它。而有了 git 这样的工具之后,我们在什么时候做了什么样的工作,是很容易证明的 —— 这对我们来说真是天大的好事。

为什么一定要掌握自学能力?

一句话解释清楚:

没有自学能力的人没有未来

有两个因素需要深入考虑:

  • 未来的日子还很长
  • 这世界进步得太快

很多人都会不由自主地去复刻父母的人生时刻表

这两个因素叠加在一起的结果就是,这世界对很多人来说,其实是越来越残忍的。

我见过太多的同龄人,早早就停止了进步,早早就被时代甩在身后,早早就因此茫然不知所措 —— 早早晚晚,你也会遇到越来越多这样的人。他们的共同特征只有一个:

没有自学能力

绝大多数人终生都饱受时间幻觉的拖累。

小时候觉得时间太长,那是幻觉;长大了觉得时间越来越快,那还是幻觉 —— 时间从来都是匀速的。最大的幻觉在于,总是以为 “时间不够了” —— 这个幻觉最坑人。许多年前,有一次我开导我老婆。她说,“啊?得学五年才行啊?!太长了!” 我说,

“你回头看看呗,想想呗,五年前你在做什么?是不是回头一看的时候,五年前就好像是昨天?道理是一样的,五年之后的某一天你回头想今天,也是 ‘一转眼五年就过去’ 了…… 只不过,你今天觉得需要时间太多,所以不肯学 —— 但是,不管你学还是不学,五年还是会 ‘一转眼就过去’ 的…… 到时候再回头,想起这事的时候,没学的你,一定会后悔 —— 事实上,你已经有很多次后悔过 ‘之前要是学了就好了’,不是吗?”


自学能力是唯一值得被不断磨练的长技。

磨练出自学能力的好处在于,无论这世界需要我们学什么的时候,我们都可以主动去学,并且还是马上开始 —— 不需要等别人教、等别人带。

哪怕有很强的自学能力的意思也并不是说,什么都能马上学会、什么都能马上学好,到最后无所不精无所不通…… 因为这里有个时间问题。无论学什么,都需要耗费时间和精力,与此同时更难的事情在于不断填补耐心以防它过早耗尽。另外,在极端的情况下,多少也面临天分问题。比如身高可能影响打篮球的表现,比如长相可能影响表演的效果,比如唱歌跑调貌似是很难修复的,比如有些人的粗心大意其实是基因决定的,等等。不过,以我的观察,无论是什么,哪怕只是学会一点点,都比不会强。哪怕只是中等水平,就足够应付生活、工作、养家糊口的需求。

靠什么呢?人么,一个都靠不上。到最后,我觉得只有一样东西真正可靠 —— 自学能力。于是,经年累月,我磨练出了一套属于我自己的本领:只要我觉得有必要,我什么都肯学,学什么都能学会到够用的程度…… 编程,我不是靠上课学会的;英语,不是哪个老师教我的;写作,也不是谁能教会我的;教书,没有上过师范课程;投资,更没人能教我 —— 我猜,也没人愿意教我…… 自己用的东西自己琢磨,挺好。

关键在于,自学这事并不难,也不复杂,挺简单的,因为它所需要的一切都很朴素。

于是,从某个层面上来看,我每天都过的很开心。为什么?因为我有未来。凭什么那么确信?因为我知道我自己有自学能力。

—— 我希望你也有

准确地讲,希望你有个更好的未来。

而现在我猜,此刻,你心中也是默默如此作想的罢。

为什么把编程当作自学的入口?

一本关于自学能力的书,若是真的能够起到作用,那么它就必须让读者在读之前和读之后不一样 —— 比如,之前可能没有自学能力,或者自学能力很差,之后就有了一定的自学能力……

道理当然很重要;可是,在传递道理的时候,例子相对来看好像更重要。

同样的道理,例子不准,人就可能会理解错;例子不精彩,人就可能听不进去;例子居然可以令人震惊,那就可以做到让听众、让读者 “永生不忘”。

所以啊,在我看来,写书讲课之前,最重要的工作,也是做得最多的事情,其实就是 “找到好例子” —— 那即意味着说,先要找到很多很多恰当合适的例子,而后再通过反复比较试验,挑出那个效果最好的例子。了解了这一点,将来你准备任何演讲,都会不由自主地多花一点时间在这方面,效果肯定比 “把幻灯片做得更花哨一些” 要好太多了罢?

后来,我选中了一个例子:“自学编程” —— “尽量只通过阅读学会编程”。

(一)

选择它的理由,首先就在于:

事实证明,*它就是无论是谁都能学会的- —— 千万别不信。

在计算机科学领域,所有的所谓 “优质教育资源” 事实上完全没有任何独特的竞争力 —— 编程领域,实际上是当今世上极为罕见的 “教育机会公平之地”。

(二)

首先,编程这个东西反正要自学 —— 不信你问问计算机专业的人,他们会如实告诉你的,学校里确实也教,但说实话都教得不太好……

其次,编程这个东西最适合 “仅靠阅读自学” —— 这个领域发展很快,到最后,新东西出来的时候,没有老师存在,任由你是谁,都只能去阅读 “官方文档”,只此一条路。

然后,也是最重要的一条,别管是不是很多人觉得编程是很难的东西,事实上它就是每个人都应该具备的技能。

再后来,不懂基本计算机操作技能的,也算是文盲,因为他们无论做什么事情,效率都太低下了,明明可以用快捷键一下子完成的事情,却非要手动大量重复……【我就是不会用快捷键的文盲】

到了最近,不懂数据分析的,也开始算作文盲了。许多年前人们惊呼信息时代来了的时候,其实暂时体会不到什么太多的不同。然而,许多年过去,互联网上的格式化数据越来越多,不仅如此,实时产出的格式化数据也越来越多,于是,数据分析不仅成了必备的能力,而且早就开始直接影响一个人的薪资水平。

你作为一个个体,每天都在产生各种各样的数据,然后时时刻刻都在被别人使用着、分析着…… 然而你自己却全然没有数据分析能力,甚至不知道这事很重要,是不是感觉很可怕?你看看周边那么多人,有多大的比例想过这事?反正那些天天看机器算法生成的信息流的人好像就是全然不在意自己正在被支配……

怎么办?学呗,学点编程罢 —— 巧了,这还真是个正常人都能学会的技能

(三)

编程作为 “讲解如何习得自学能力的例子” 最好的地方在于,这个领域的知识结构,最接近每个人所面对的人生中的知识结构。

这是什么意思呢?

编程入门的门槛之所以高,有个比较特殊的原因:

它的知识点结构不是线性的

很遗憾,编程所涉及到的知识点没办法这样组织 —— 就是不行。编程教材之所以难以读懂,就是因为它的各章中的知识点结构不是线性排列的。你经常在某一章读到不知道后面第几章才可能讲解清楚的概念。

这种现象,可以借用一个专门的英文概念,叫做 “Forward References” —— 原本是计算机领域里的一个术语。为了配合当前的语境,姑且把它翻译为 “过早引用” 罢,或者 “前置引用” 也行。

学校里的课本,都很严谨 —— 任何概念,未经声明就禁止使用。所以,学完一章,就能学下一章;跳到某一章遇到不熟悉的概念,往前翻肯定能找到……

在学校里习惯了这种知识体系的人,离开学校之后马上抓瞎 —— 社会的知识结构不仅不是这样的,而且几乎全都不是这样的。工作中、生活里,充满了各式各样的 “过早引用”。为什么总是要到多年以后你才明白父母曾经说过的话那么有道理?为什么总要到孩子已经长大之后才反应过来当初自己对孩子做错过很多事情?为什么在自己成为领导之前总是以为他们只不过是在忽悠你?为什么那么多人创业失败了之后才反应过来当初投资人提醒的一些观念其实是千真万确的?—— 因为很多概念很多观念是 “过早引用”,在当时就是非常难以理解……

自学编程在这方面的好处在于,在自学的过程中,其实你相当于过了一遍 “模拟人生” —— 于是,面对同样的 “过早引用”,你不会觉得那么莫名其妙,你有一套你早已在 “模拟人生” 中练就的方法论去应对。

(四)

另外一个把编程作为 “讲解如何习得自学能力的例子” 最好的地方在于,你在这个过程中将不得不习得英语 —— 起码是英文阅读能力,它能让你在不知不觉中 “脱盲”。

最好的 Python 教程,是官方网站上的 The Python Tutorial,读它就够了。我个人完全没兴趣从头到尾写一本 Python 编程教材,不仅因为人家写得真好,而且它就放在那里。

我曾经专门写过一本书发布在网上,叫《人人都能用英语》。其中的观点就是,大多数人之所以在英语这事上很矬,是因为他们花无数的时间去 “学”,但就是 “不用”。学以致用,用以促学。可就是不用,无论如何就是不用,那英语学了那么多年能学好吗?

自学编程的一个 “副作用” 就是,你不得不用英语。而且还是天天用,不停地用。

当年我上大学的时候,最初英语当然也不好。不过,因为想读当时还是禁书的《动物庄园》(Animal Farm),就只好看原版(当时好不容易搞到的是本英法对照版)…… 然后英语阅读就基本过关了。

这原理大抵上是这样,刚开始,英语就好像一层毛玻璃,隔在你和你很想要了解的内容之间。然而,由于你对那内容的兴趣和需求是如此强烈,乃至于即便隔着毛玻璃你也会挣扎着去看清楚…… 挣扎久了(其实没两天就不一样),你的 “视力” 就进化了,毛玻璃还在那里,但你好像可以穿透它看清一切……

自学编程,也算是一举两得了!

(五)

当然,把编程作为 “讲解如何习得自学能力的例子”,实在是太好了的最重要原因在于,自学编程对任何人来说都绝对是:

  • 现实的(Practical)
  • 可行动的(Actionable)
  • 并且还是真正是可达成的(Achievable)

最重要的就是最后这个 “可达成的”。虽然对读者和作者来说,一个做到没那么容易,另一个讲清楚也非常难,但是,既然是所有人都 “可达成的”,总得试试吧?但是,请相信我,这事比减肥容易多了 —— 毕竟,你不是在跟基因作斗争。

另外,自学者最大的感受就是万物相通。他们经常说的话有这么一句:“…… 到最后,都是一样的呢。”

(六)

最后一个好处,一句话就能说清楚,并且,随着时间的推移,你对此的感触会越来越深:

在这个领域里,自学的人最多……

没有什么比这句话更令人舒心的了:相信我,你并不孤独

只靠阅读习得新技能

习得自学能力的终极目标就是:

有能力只靠阅读就能习得新技能。

幸亏后来我渐渐明白,且越来越相信:

自己生活工作学习上遇到的所有疑问,书本里应该都有答案 —— 起码有所参考。

“不是什么东西都可以从书本里学到的……” 这话听起来那么有道理,只不过是因为自己读书不够多不够对而已。

过了 25 岁,我放弃了读小说,虚构类作品,我只选择看电影;而非虚构类作品,我选择尽量只读英文书,虽然那时候买起来很贵也很费劲,但我觉得值 —— 英文世界和中文世界的文化风格略有不同。在英文世界里,你看到的正常作者好像更多地把 “通俗易懂”、“逻辑严谨” 当作最基本的素养;而在中文世界里,好像 “故弄玄虚”、“偷梁换柱” 更常见一些;在英文世界里,遇到读不懂的东西可以很平静地接受自己暂时的愚笨,心平气和地继续努力就好;在中文世界里,遇到装神弄鬼欺世盗名的,弄不好最初根本没认出来,到最后跟 “认贼作父” 一样令人羞辱难当不堪回首。

知识原本就应该无国界…… 不是吗?不是吗!

再说,这些年我其实还读了不少中国人写的英文书呢,比如,张纯如的书很值得一看;郑念的 Life and Death in Shanghai,真的很好很好。我也读了不少老外写的关于中国的书 —— 这些年我一直推荐费正清的剑桥中国史(The Cambridge History of China),当然有中文版的,不过,能读英文版的话感受很不一样。

当然,英文书里同样烂书也很多,烂作者也同样一大堆,胡说八道欺世盗名的一大串…… 但总体上来看,非小说类著作质量的确更高一点。

读书越多越明白读书少会被忽悠…… 很多人真的会大头捣蒜一般地认同 “不是什么东西都可以从书本里学到的……”

有个很有趣的现象:

人么,只要识字,就忍不住阅读……

只不过,人们阅读的选择很不同而已。有自学能力的人和没有自学能力的人,在这一点上很容易分辨:

  • 有自学能力的人,选择阅读 “有繁殖能力” 的内容;
  • 没有自学能力的人,阅读只是为了消磨时光……

我把那些能给你带来新视野,能让你改变思考模式,甚至能让你拥有一项新技能的内容称之为 “有繁殖能力的内容”。

人都一样,拥有什么样的能力之后,就会忍不住去用,甚至总是连下意识中也要用。

那些靠阅读机器算法推送的内容而杀时间的人,恰恰就是因为他们有阅读能力才去不断地读,读啊读,像是那只被打了兴奋剂后来死在滚轮上的小白鼠。如果这些人哪怕有一点点自学能力,那么他们很快就会分辨出自己正在阅读的东西不会刺激自己的产出,只会消磨自己的时间;那么,他们就会主动放弃阅读那些杀时间的内容,把那时间和精力自然而然地用在筛选有繁殖能力的内容,让自己进步,让自己习得更多技能上去了。

所以,只要你有一次 “只靠阅读习得一项新技能” 的经验,你就变成另外一个人了。你会不由自主、哪怕下意识里都会去运用你新习得的能力…… 从这个角度看,自学很上瘾!能上瘾,却不仅无害,还好处无穷,这样的好事,恐怕也就这一个了罢。

仅靠阅读学会新技能不仅是可能的,并且,你随后会发现的真相是:

绝大多数情况下,没人能教你,也不一定有人愿意教你…… 到最后,你想学会或你必须学会什么东西的时候,**你只能靠阅读!*- —— 因为其实你谁都靠不上……

讲真,你没有选择,只靠阅读习得新技能,这是你唯一的出路。

开始阅读前的一些准备

内容概要

关于 Python 编程的第一部分总计 7 章,主要内容概括为:

  1. 以布尔值为入口开始理解程序本质
  2. 了解值的分类和运算方法
  3. 简要了解流程控制的原理
  4. 简要了解函数的基本构成
  5. 相对完整地了解字符串的操作
  6. 了解各种容器的基础操作
  7. 简要了解文件的读写操作

阅读策略

首先,不要试图一下子就全部搞懂。这不仅很难,在最初的时候也完全没必要

因为这部分的知识结构中,充满了 “过早引用”。请在第一遍粗略完成第 1 部分中的 E1 ~ E7 之后,再去阅读《如何从容应对 “过早引用”?》。

其次,这一部分,注定要反复阅读若干遍

在开始之前,要明确这一部分的阅读目标。

这一部分的目标,不是让你读完之后就可以开始写程序;也不是让你读完之后就对编程或者 Python 编程有了完整的了解,甚至不是让你真的学会什么…… 这一部分的目标,只是让你 “脱盲”。

不要以为脱盲是很容易的事情。你看,所有人出生的时候,都天然是 “文盲”;人们要上好多年的学,才能够真正 “脱盲” —— 仔细想想吧,小学毕业的时候,所有人就真的彻底脱盲了吗?

以中文脱盲为例,学字的同时,还要学习笔划;为了学更多的字,要学拼音,要学如何使用《新华字典》……

学会了一些基础字之后,要学更多的词,而后在练习了那么多造词造句之后,依然会经常用错…… 你看,脱盲,和阅读能力强之间距离很长呢;不仅如此,阅读能力强和写作能力强之间的距离更长……

反复阅读这一部分的结果是:

  • 你对基本概念有了一定的了解
  • 你开始有能力相对轻松地阅读部分官方文档
  • 你可以读懂一些简单的代码

仅此而已。

心理建设

当我们开始学习一项新技能的时候,我们的大脑会不由自主地紧张。可这只不过是多年之间在学校里不断受挫的积累效应 —— 学校里别的地方不一定行,可有个地方特别行:给学生制造全方位、无死角、层层递进的挫败感。

可是,你要永远记住两个字:

别怕!

用四个字也行:

啥也别怕!

六个字也可以:

没什么可怕的!

我遇到最多的孱弱之语大抵是这样的:

我一个文科生……

哈哈,从某个层面望过去,其实吧,编程既不是文科也不是理科…… 它更像是 “手工课”。你越学就越清楚这个事实,它就好像是你做木工一样,学会使用一个工具,再学会使用另外一个工具,其实总共就没多少工具。然后,你更多做的是各种拼接的工作,至于能做出什么东西,最后完全靠你的想象力……

十来岁的孩子都可以学会的东西,你怕什么?

别怕,无论说给自己,还是讲给别人,都是一样的,它可能是人生中最重要的鼓励词。

入口

乔治・布尔

1833 年,一个 18 岁的英国小伙脑子里闪过一个念头:

逻辑关系应该能用符号表示。

这个小伙子叫乔治・布尔(George Boole,其实之前就提到过我的这位偶像),于 1815 年出生于距离伦敦北部 120 英里之外的一个小镇,林肯。父亲是位对科学和数学有着浓厚兴趣的鞋匠。乔治・布尔在父亲的影响下,靠阅读自学成才。14 岁的时候就在林肯小镇名声大噪,因为他翻译了一首希腊语的诗歌并发表在本地的报纸上。

到了 16 岁的时候,他被本地一所学校聘为教师,那时候他已经在阅读微积分书籍。19 岁的时候布尔创业了 —— 他办了一所小学,自任校长兼教师。23 岁,他开始发表数学方面的论文。他发明了 “操作演算”,即,通过操作符号来研究微积分。他曾经考虑过去剑桥读大学,但后来放弃了,因为为了入学他必须放下自己的研究,还得去参加标准本科生课程。这对一个长期只靠自学成长的人来说,实在是太无法忍受了。

1847 年,乔治 32 岁,出版了他人生的第一本书籍,THE MATHEMATICAL ANALYSIS OF LOGIC —— 18 岁那年的闪念终于成型。这本书很短,只有 86 页,但最终它竟然成了人类的瑰宝。在书里,乔治・布尔很好地解释了如何使用代数形式表达逻辑思想。

1849 年,乔治・布尔 34 岁,被当年刚刚成立的女皇学院(Queen's College)聘请为第一位数学教授。随后他开始写那本最著名的书,AN INVESTIGATION OF THE LAWS OF THOUGHT。他在前言里写到:

“The design of the following treatise is to investigate the fundamental laws of those operations of the mind by which reasoning is performed; to give expression to them in the symbolical language of a Calculus, and upon this foundation to establish the science of Logic and construct its method; …”

“本书论述的是,探索心智推理的基本规律;用微积分的符号语言进行表达,并在此基础上建立逻辑和构建方法的科学……”

在大学任职期间,乔治・布尔写了两本教科书,一本讲微分方程,另外一本讲差分方程,而前者,A TREATISE ON DIFFERENTIAL EQUATIONS,直到今天,依然难以超越。

george-boole-1864

乔治・布尔于 1864 年因肺炎去世。

乔治・布尔在世的时候,人们并未对他的布尔代数产生什么兴趣。直到 70 年后,克劳德・香农(Claude Elwood Shannon)发表那篇著名论文,A SYMBOLIC ANALYSIS OF RELAY AND SWITCHING CIRCUITS 之后,布尔代数才算是开始被大规模应用到实处。

有本书可以闲暇时间翻翻,The Logician and the Engineer: How George Boole and Claude Shannon Created the Information Age。可以说,没有乔治・布尔的布尔代数,没有克劳德・香农的逻辑电路,就没有后来的计算机,就没有后来的互联网,就没有今天的信息时代 —— 世界将会怎样?

2015 年,乔治・布尔诞辰 200 周年,Google 设计了专门的 Logo 纪念这位为人类作出巨大贡献的自学奇才。

george-boole-google-doodle-2015

Google Doodle 的寄语是这样的:

A very happy 11001000th birthday to genius George Boole!

布尔运算

然而,这些都不是最重要的差异 —— 最重要的差异在于计算机能做布尔运算(Boolean Operations)。

于是,一旦代码编写好之后,计算机在执行的过程中,除了可以 “按照顺序执行任务” 之外,还可以 “根据不同情况执行不同的任务”,比如,“如果条件尚未满足则重复执行某一任务”。

计算器和计算机都是电子设备,但计算机更为强大的原因,用通俗的说法就是它 “可编程”(Programable)—— 而所谓可编程的核心就是布尔运算及其相应的流程控制(Control Flow);没有布尔运算能力就没有办法做流程控制;没有流程控制就只能 “按顺序执行”,那就显得 “很不智能”……

布尔值

在 Python 语言中,布尔值(Boolean Value)用 TrueFalse 来表示。

注意:请小心区分大小写 —— 因为 Python 解释器是对大小写敏感的,对它来说,Truetrue 不是一回事。

任何一个逻辑表达式都会返回一个布尔值

逻辑操作符

Python 语言中的逻辑操作符(Logical Operators)如下表所示 —— 为了理解方便,也可以将其称为 “比较操作符”。

比较操作符意义示例布尔值
==等于1 == 2False
!=不等于1 != 2True
>大于1 > 2False
>=大于等于1 >= 1True
<小于1 < 2True
<=小于等于1 <= 2True
in属于'a' in 'basic'True

布尔运算操作符

以上的例子中,逻辑操作符的运算对象(Operands)是数字值和字符串值。

而针对布尔值进行运算的操作符很简单,只有三种:与、或、非:

分别用 andornot 表示

注意:它们全部是小写。因为布尔值只有两个,所以布尔运算结果只有几种而已,如下图所示:

boolean-operators.png

流程控制

有了布尔运算能力之后,才有根据情况决定流程的所谓流程控制(Control Flow)的能力。

import random
r = random.randrange(1, 1000)

if r % 2 == 0:
    print(r, 'is even.')
else:
    print(r, 'is odd.')

现在看代码,先忽略其它的部分,只看关键部分:

    ...
    if r % 2 == 0:
        ...
    else:
        ...

这个 if/else 语句,完成了流程的分支功能。% 是计算余数的符号,如果 r 除以 2 的余数等于 0,那么它就是偶数,否则,它就是奇数 —— 写成布尔表达式,就是 r % 2 == 0

这一次,你看到了单个等号 =r = random.randrange(1, 1000)

这个符号在绝大多数编程语言中都是 “赋值”(Assignment)的含义。

r = 2 之中,r 是一个名称为 r变量(Variable)—— 现在只需要将变量理解为程序保存数值的地方;而 = 是赋值符号,2 是一个整数常量(Literal)。

语句 r = 2 用自然语言描述就是:

“把 2 这个值保存到名称为 r 的变量之中”。

所谓算法

以上的算法可以改进(程序员们经常用的词汇是 “优化”):

2 作为除数开始试,试到 根号 n 之后的一个整数就可以了……

for n in range(2, 100):
    if n == 2:
        print(n)
        continue
    for i in range(2, int(n ** 0.5)+1): #为什么要 +1 以后再说…… n 的 1/2 次方,相当于根号 n。
        if (n % i) == 0:
            break
    else:
        print(n)

你看,寻找更有效的算法,或者说,不断优化程序,提高效率,最终是程序员的工作,不是编程语言本身的工作。关于判断质数最快的算法,可以看 Stackoverflow 上的讨论,有更多时间也可以翻翻 Wikipedia

到最后,所有的工具都一样,效用取决于使用它的人。所以,学会使用工具固然重要,更为重要的是与此同时自己的能力必须不断提高。

虽然写代码这事刚开始学起来好像门槛很高,那只不过是幻觉,其实门槛比它更高的多的去了。到最后,它就是个最基础的工具,还是得靠思考能力 —— 这就好像识字其实挺难的 —— 小学初中高中加起来十来年,我们才掌握了基本的阅读能力;可最终,即便是本科毕业、研究生毕业,真的能写出一手好文章的人还是少之又少一样 —— 因为用文字值得写出来的是思想,用代码值得写出来的是创造,或者起码是有意义的问题的有效解决方案。有思想,能解决问题,是另外一门手艺 —— 需要终生精进的手艺。

所谓函数

我们已经反复见过 print() 这个函数(Functions)了。它的作用很简单,就是把传递给它的值输出到屏幕上 —— 当然,事实上它的使用细节也很多,以后慢慢讲。

现在,最重要的是初步理解一个函数的基本构成。关于函数,相关的概念有:函数名(Function Name)、参数(Parameters)、返回值(Return Value)、调用(Call)。

拿一个更为简单的函数作为例子,abs()。它的作用很简单:接收一个数字作为参数,经过运算,返回该数字的绝对值。

>>> a=abs(-3.1415)
>>> print(a)
3.1415
>>>

在以上的代码的第 1 行中,

  • 我们调用了一个函数名abs 的函数;写法是 abs(-3.1415926)
  • 这么写,就相当于向它传递了一个参数,其值为:-3.1415926
  • 该函数接收到这个参数之后,根据这个参数的在函数内部进行了运算
  • 而后该函数返回了一个值,返回值为之前接收到的参数的值的绝对值 3.1415926
  • 而后这个被保存到变量 a 之中。

从结构上来看,每个函数都是一个完整的程序,因为一个程序,核心构成部分就是输入处理输出

  • 它有输入 —— 即,它能接收外部通过参数传递的值;
  • 它有处理 —— 即,内部有能够完成某一特定任务的代码;尤其是,它可以根据 “输入” 得到 “输出”;
  • 它有输出 —— 即,它能向外部输送返回值……

被调用的函数,也可以被理解为子程序(Sub-Program)—— 主程序执行到函数调用时,就开始执行实现函数的那些代码,而后再返回主程序……

判断一个数是否为质数

def is_prime(n):       # 定义 is\_prime(),接收一个参数
    if n < 2:          # 开始使用接收到的那个参数(值)开始计算……
        return False   # 不再是返回给人,而是返回给调用它的代码……
    if n == 2:
        return True
    for m in range(2, int(n**0.5)+1):
        if (n%m) == 0:
            return False
    else:
        return True

for i in range(80, 110):
    if is_prime(i):    # 调用 is_prime() 函数,
        print(i)       # 如果返回值为 True,则向屏幕输出 i

细节补充

语句

一个完整的程序,由一个或者多个语句(Statements)构成。通常情况下,建议每一行只写一条语句。

语句块

在 Python 语言中,行首空白(Leading whitespace,由空格 ' ' 或者 Tab 构成)有着特殊的含义。

如果有行首空白存在,那么,Python 将认为这一行与其他邻近有着相同行首空白的语句同属于一个语句块 —— 而一个语句块必然由一个行末带有冒号 : 的语句起始。同属于一个语句块中的语句,行首空白数量应该相等。这看起来很麻烦,可实际上,程序员一般都使用专门的文本编辑器,比如 Visual Studio Code,其中有很多的辅助工具,可以让你很方便地输入具备一致性的行首空白。

注意:在同一个文件里,不建议混合使用 Tab 和 Space;要么全用空格,要么全用制表符。

注释

在 Python 程序中可以用 # 符号标示注释语句。

所谓的注释语句,就是程序文件里写给人看而不是写给计算机看的部分。本节中的代码里就带着很多的注释。

操作符

在本节,我们见到的比较操作符可以比较它左右的值,而后返回一个布尔值。

我们也见过两个整数被操作符 % 连接,左侧作为被除数,右侧作为除数,11 % 3 这个表达式的值是 2。对于数字,我们可用的操作符有 +-*///%** —— 它们分别代表加、减、乘、除、商、余、幂。

赋值符号与操作符的连用

你已经知道变量是什么了,也已经知道赋值是什么了。于是,你看到 x = 1 就明白了,这是为 x 赋值,把 1 这个值保存到变量 x 之中去。

但是,若是你看到 x += 1,就迷惑了,这是什么意思呢?

这只是编程语言中的一种惯用法。它相当于 x = x + 1

总结

以下是这一章中所提到的重要概念。了解它们以及它们之间的关系,是进行下一步的基础。

  • 数据:整数、布尔值;操作符;变量、赋值;表达式
  • 函数、子程序、参数、返回值、调用
  • 流程控制、分支、循环
  • 算法、优化
  • 程序:语句、注释、语句块
  • 输入、处理、输出
  • 解释器

你可能已经注意到了,这一章的小节名称罗列出来的话,看起来像是一本编程书籍的目录 —— 只不过是概念讲解顺序不同而已。事实上还真的就是那么回事。

这些概念,基本上都是独立于某一种编程语言的(Language Independent),无论将来你学习哪一种编程语言,不管是 C++,还是 JavaScript,抑或是 Golang,这些概念都在那里。

学会一门编程语言之后,再学其它的就会容易很多 —— 而且,当你学会了其中一个之后,早晚你会顺手学其它的,为了更高效使用微软办公套件,你可能会花上一两天时间研究一下 VBA;为了给自己做个网页什么的,你会顺手学会 JavaScript;为了修改某个编辑器插件,你发现人家是用 Ruby 写的,大致读读官方文档,你就可以下手用 Ruby 语言了;为了搞搞数据可视化,你会发现不学会 R 语言有点不方便……

你把这些概念装在脑子里,而后就会发现几乎所有的编程入门教学书籍结构都差不多是由这些概念构成的。因为,所有的编程语言基础都一样,所有的编程语言都是我们指挥计算机的工具。无论怎样,反正都需要输入输出,无论什么语言,不可能没有布尔运算,不可能没有流程控制,不可能没有函数,只要是高级语言,就都需要编译器…… 所以,掌握这些基本概念,是将来持续学习的基础。

值及其相应的运算

从结构上来看,一切的计算机程序,都由且只由两个最基本的成分构成:

  • 运算(Evaluation)
  • 流程控制(Control Flow)

没有流程控制的是计算器而已;有流程控制的才是可编程设备。

从本质上看,程序里的绝大多数语句包含着运算(Evaluation),即,在对某个值进行评价。这里的 “评价”,不是 “判断某人某事的好坏”,而是 “计算出某个值究竟是什么” —— 所以,我们用中文的 “运算” 翻译这个 “Evaluation” 可能表达得更准确一些。

在程序中,被运算的可分为常量(Literals)和变量(Variables)。

值的类型

在编程语言中,总是包含最基本的三种数据类型:

  • 布尔值(Boolean Value)
  • 数字(Numbers):整数(Int)、浮点数(Float)、复数(Complex Numbers)
  • 字符串(Strings)

运算的一个默认法则就是,通常情况下应该是相同类型的值才能相互运算

在不得不对不同类型的值进行运算之前,总是要事先做 Type Casting(类型转换)。比如,

  • 将字符串转换为数字用 int()float()
  • 将数字转换成字符串用 str()

另外,即便是在数字之间进行计算的时候,有时也需要将整数转换成浮点数字,或者反之:

  • 将整数转换成浮点数字用 float()
  • 将浮点数字转换成整数用 int()

有个函数,type(),可以用来查看某个值属于什么类型。

操作符

针对不同类型的数据,有各自专用的操作符

数值操作符

针对数字进行计算的操作符有加减乘除商余幂:+-*///%**

从优先级来看,这些操作符中:

  • 对两个值进行操作的 +- 的优先级最低;
  • 稍高的是 *///%
  • 更高的是对单个值进行操作的 +-
  • 优先级最高的是 **

完整的操作符优先级列表,参见官方文档:

https://docs.python.org/3/reference/expressions.html#operator-precedence

布尔值操作符

针对布尔值,操作符有andornot

它们之中,优先级最低的是或 or,然后是与 and, 优先级最高的是非 not

逻辑操作符

数值之间还可以使用逻辑操作符,1 > 2 返回布尔值 False。逻辑操作符有:<(小于)、<=(小于等于)、 >(大于)、>=(大于等于)、!=(不等于)、==(等于)。

逻辑操作符的优先级,高于布尔值的操作符,低于数值计算的操作符。 即:数值计算的操作符优先级最高,其次是逻辑操作符,布尔值的操作符优先级最低。