https://github.com/selfteaching/the-craft-of-selfteaching
# pseudo-code of selfteaching in Python
def teach_yourself(anything):
while not create():
learn()
practice()
return teach_yourself(another)
teach_yourself(coding)
#前言
自学能力,对每个个体来说,是这个变化频率和变化幅度都在不断加大的时代里最具价值的能力。具备这个能力,不一定能直接增加一个人的幸福感(虽然实际上常常确实能),但它一定会缓解甚至消除一个人的焦虑。若是在一个以肉眼可见的方式变化着的环境里生存,却心知肚明自己已然原地踏步许久,自己正在被这个时代甩在身后,谁能不焦虑呢?
完成这本书的内容,起码会习得一个新技能:编程。
绝大多数人,终其一生都没有自学过什么。他们也不是没学过,也不是没辛苦过,但事实却是:他们在有人教、有人带、有人逼的情况下都没真学明白那些基础知识……更可怕的是,他们学的那些东西中,绝大多数终其一生只有一个用处:考试。于是,考试过后,那些东西就“考过即弃”了……不得不承认, 应试教育的确是磨灭自学能力的最有效方法。
#为什么一定要掌握自学能力?
拿中国地区做例子,根据世界银行的数据统计,中国人在出生时的寿命预期,从 1960 年的 43.73 岁,增长到了 2016 年的 76.25 岁,56 年间的增幅竟然有 74.39% 之多!
如此发展下去,虽然人类不大可能永生不死,但平均寿命依然在持续延长是个不争的事实。与上一代不同,现在的千禧一代,需要面对的是百岁人生 —— 毫无疑问,不容置疑。
这么长的人生,比默认的想象中可能要多出近一倍的人生,再叠加上另外一个因素 —— 这是个变化越来越快的世界 —— 会是什么样子?
有一个统计指数,叫做人类发展指数(Human Development Index),它的曲线画出来,怎么看都有即将成为指数级上升的趋势。
纽约联邦储蓄银行在 2012 年做过一个调查,发现人们的职业与自己大学所学专业相符的比例连 30% 都不到。而且,我猜,这个比例会持续下降的 —— 因为这世界变化快,因为大多数教育机构与世界发展脱钩的程度只能越来越严重……
#为什么把编程当作自学的入口?
编程作为 “讲解如何习得自学能力的例子”,实在是太好了。
- 首先,编程这个东西反正要自学 —— 不信你问问计算机专业的人,他们会如实告诉你的,学校里确实也教,但说实话都教得不太好……
- 其次,编程这个东西最适合 “仅靠阅读自学” —— 这个领域发展很快,到最后,新东西出来的时候,没有老师存在,任由你是谁,都只能去阅读 “官方文档”,只此一条路。
- 然后,也是最重要的一条,别管是不是很多人觉得编程是很难的东西,事实上它就是每个人都应该具备的技能。
编程入门的门槛之所以高,有个比较特殊的原因: 它的知识点结构不是线性的。
我们在中小学里所遇到的教科书,其中每个章节所涉及到的知识点之间,全都是线性关联。第一章学好了,就有基础学第二章;在第二章的概念不会出现在第一章之中……
很遗憾,编程所涉及到的知识点没办法这样组织 —— 就是不行。编程教材之所以难以读懂,就是因为它的各章中的知识点结构不是线性排列的。你经常在某一章读到不知道后面第几章才可能讲解清楚的概念。
比如,几乎所有的 Python 编程书籍上来就举个例子:
print('Hello, world!')
姑且不管这个例子是否意义非凡或者意义莫名,关键在于, print()
是个函数,而「函数」这个概念,不可能一上来就讲清楚,只能在后面若干章之后才开始讲解……
想要理解当前的知识点,需要依赖对以后才能开始学习的某个甚至多个知识点的深入了解……
这种现象,可以借用一个专门的英文概念,叫做 “Forward References” —— 原本是计算机领域里的一个术语。为了配合当前的语境,姑且把它翻译为 “过早引用” 罢,或者 “前置引用” 也行。
#只靠阅读习得新技能
过了 25 岁,我放弃了读小说,虚构类作品,我只选择看电影;而非虚构类作品,我选择尽量只读英文书,虽然那时候买起来很贵也很费劲,但我觉得值 —— 英文世界和中文世界的文化风格略有不同。在英文世界里,你看到的正常作者好像更多地把 “通俗易懂”、“逻辑严谨” 当作最基本的素养;而在中文世界里,好像 “故弄玄虚”、“偷梁换柱” 更常见一些;在英文世界里,遇到读不懂的东西可以很平静地接受自己暂时的愚笨,心平气和地继续努力就好;在中文世界里,遇到装神弄鬼欺世盗名的,弄不好最初根本没认出来,到最后跟 “认贼作父” 一样令人羞辱难当不堪回首。
那些靠阅读机器算法推送的内容而杀时间的人,恰恰就是因为他们有阅读能力才去不断地读,读啊读,像是那只被打了兴奋剂后来死在滚轮上的小白鼠。如果这些人哪怕有一点点自学能力,那么他们很快就会分辨出自己正在阅读的东西不会刺激自己的产出,只会消磨自己的时间;那么,他们就会主动放弃阅读那些杀时间的内容,把那时间和精力自然而然地用在筛选有繁殖能力的内容,让自己进步,让自己习得更多技能上去了。
#开始阅读前的一些准备
#内容概要
关于 Python 编程的第一部分总计 7 章,主要内容概括为:
- 以 布尔值 为入口开始理解 程序本质
- 了解值的 分类和运算方法
- 简要了解 流程控制 的原理
- 简要了解 函数 的基本构成
- 相对完整地了解 字符串的操作
- 了解各种 容器 的基础操作
- 简要了解 文件的读写 操作
#阅读策略
- 首先,不要试图一下子就全部搞懂。这不仅很难, 在最初的时候也完全没必要。 因为这部分的知识结构中,充满了 "过早引用"。请在第一遍粗略完成第 1 部分中的 E1 ~ E7 之后,再去阅读《如何从容应对 "过早引用"?》。
- 其次,这一部分,注定要 反复阅读若干遍。
反复阅读这一部分的结果是:
- 你对基本概念有了一定的了解
- 你开始有能力相对轻松地阅读部分官方文档
- 你可以读懂一些简单的代码
#心理建设
当我们开始学习一项新技能的时候,我们的大脑会不由自主地紧张。可这只不过是多年之间在学校里不断受挫的积累效应。学校里别的地方不一定行,可有个地方特别行:给学生制造全方位、无死角、层层递进的挫败感。
可是,你要永远记住两个字:
别怕!
用四个字也行:
啥也别怕!
六个字也可以:
没什么可怕的!
别怕 ,无论说给自己,还是讲给别人,都是一样的,它可能是人生中最重要的鼓励词。
#入口
#乔治・布尔
逻辑关系 应该能用 符号 表示。
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,直到今天,依然难以超越。
乔治・布尔在世的时候,人们并未对他的布尔代数产生什么兴趣。直到 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 纪念这位为人类作出巨大贡献的自学奇才。
Google Doodle 的寄语是这样的:
A very happy 11001000 th birthday to genius George Boole!
#布尔运算
计算机能做 布尔运算 (Boolean Operations)。
计算器和计算机都是电子设备,但计算机更为强大的原因,用通俗的说法就是它“ 可编程 ”(Programable)。而所谓可编程的核心就是
- 布尔运算
- 流程控制 (Control Flow)
#布尔值
在 Python 语言中, 布尔值 (Boolean Value)用 True
和 False
来表示。
*注意*:请小心区分大小写——因为 Python 解释器是对大小写敏感的,对它来说, True
和 true
不是一回事。
任何一个 逻辑表达式 都会返回一个 布尔值 。
#逻辑操作符
Python 语言中的 逻辑操作符 (Logical Operators)如下表所示——为了理解方便,也可以将其称为“/比较操作符/”。
比较操作符 | 意义 | 示例 | 布尔值 |
---|---|---|---|
== |
等于 | 1 = 2= |
False |
!= |
不等于 | 1 ! 2= |
True |
> |
大于 | 1 > 2 |
False |
>= |
大于等于 | 1 > 1= |
True |
< |
小于 | 1 < 2 |
True |
<= |
小于等于 | 1 < 2= |
True |
in |
属于 | 'a' in 'basic' |
True |
-
布尔运算操作符
以上的例子中,逻辑操作符的*运算对象*(Operands)是数字值和字符串值。
而针对布尔值进行运算的操作符很简单,只有三种:与、或、非:
分别用
and=、=or=、=not
表示注意 :它们全部是小写。因为布尔值只有两个,所以布尔运算结果只有几种而已,如下图所示:
◎ 布尔运算结果
#流程控制
有了布尔运算能力之后,才有/根据情况决定流程/的所谓*流程控制*(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
- 对两个值进行操作的
-
-
布尔值操作符
针对布尔值,操作符有=与=、=或=、=非=:=and=、 =or=、=not=。
它们之中,优先级最低的是或
or=,然后是与 =and
, 优先级最高的是非 =not=。
-
逻辑操作符
数值之间还可以使用逻辑操作符,=1 > 2= 返回布尔值
False=。逻辑操作符有:=<=(小于)、=<==(小于等于)、 =>=(大于)、=>==(大于等于)、
!==(不等于)、====(等于)。逻辑操作符的优先级,高于布尔值的操作符,低于数值计算的操作符。 即:数值计算的操作符优先级最高,其次是逻辑操作符,布尔值的操作符优先级最低。