文章目录
- 《Think Python 2e》学习精粹(一): 程序之道
- 《Think Python 2e》学习精粹(二): 变量、表达式和语句
- 《Think Python 2e》学习精粹(三): 函数
- 《Think Python 2e》学习精粹(四): 案例研究—接口设计
- 《Think Python 2e》学习精粹(五):条件和递归
- 《Think Python 2e》学习精粹(六):有返回值的函数
- 《Think Python 2e》学习精粹(七): 迭代
- 本书的目标:教你像计算机科学家一样思考;
- 计算机科学家的思考方式:使用形式语言表示思想,将零件组成系统,观察复杂系统的行为、形成假设并且对预测进行检验;
《Think Python 2e》学习精粹(一): 程序之道
1、什么是程序
- 程序 :一系列说明如何执行计算(computation)的指令;
-
不同的编程语言具体细节不同,但是有很多相通的地方:
- 输入(input);
- 输出(output);
- 数学(math):执行基本的数学运算;
- 有条件执行(conditional execution);
- 重复(repetition);
2、运行Python
- 两个在线运行Python的网页:
- dooccn;
- Pythonanywhere(教程推荐);
3、第一个程序
>>> print('Hello, World!')
Hello, World!
4、算术运算符
- 运算符(operators):代表加法和乘法等运算的特殊符号;
- 运算符 + 、- 、*和/分别执行加法、减法、乘法和除法;
>>> 40 + 2
42
>>> 43 - 1
42
>>> 6 * 7
42
>>> 84 / 2
42.0
- 运算符**执行乘方运算;
>>> 2**4 + 26
42
5、值和类型
- 值(value):程序处理的基本数据;
- 值的 类型(types) : 整型数(integer),浮点数(floating point number), 字符串(string);
- 使用内置函数type(),可以得到某一个值的类型;
>>> type(2)
<class 'int'>
>>> type(42.0)
<class 'float'>
>>> type('Hello, World!')
<class 'str'>
- 用逗号分隔的几个整形数是整形数序列;
>>> 1, 23, 456
(1, 23, 456)
6、形式语言和自然语言
- 自然语言(natural language) :人们交流所使用的语言;
- 形式语言(formal language):人类为了特殊用途而设计出来的;
- 编程语言(programming language):被设计用于表达计算的形式语言;
-
通常来说,形式语言都有严格的语法规则,这个规则包含记号(Tokens) 和 结构(structure):
- Tokens需要是合法的,比如不能在变成语言中使用全角符号或者中文;
- structure其实是tokens之间的组合方式;在进行编程的时候不能使用规定之外的结构,结构都是有严格规定的;
-
形式语言与自然语言的不同之处:
- 歧义:形式语言通常是没有歧义的;
- 冗余:形式语言包含很少的冗余,简洁明了;
- 字面性:自然语言通常有很多暗喻,这在形式语言中是没有的;
7、调试
- 调试(debugging):追踪错误的过程。
《Think Python 2e》学习精粹(二): 变量、表达式和语句
- 编程语言最强大的特性之一,就是具备操作变量的能力;
1、赋值语句
- 赋值语句(assignment statement):(新建变量,)为变量赋值;
>>> message = 'And now for something completely differentt'
>>> n = 17
>>> pi = 3.141592653589793
- 状态图(state diagram):展示了每个变量所处的状态;
2、变量名
- 变量名:存放值,一般选用有意义的名字—它们可以记录该变量的用途;
- 变量名命名规定:
- 变量名可以任意长;
- 变量名可以包括字母、数字和下划线;
- 变量名不能以数字开头,但可以是下划线;
- 变量名大小写字母都被接受,惯例只用小写字母;
- 变量名不能用关键词;
>>> pi = 3.14
>>> _pi = 3.14
>>> my name = 'Shaofeng'
File "<stdin>", line 1
my name = 'Shaofeng'
^
SyntaxError: invalid syntax
>>> myname = 'Shaofeng'
>>> 51job = '51job'
File "<stdin>", line 1
51job = '51job'
^
SyntaxError: invalid syntax
>>> MyName = 'Shaofeng'
- 关键字(keywords):解释器使用关键字识别程序的结构;
>>> class = 50
File "<stdin>", line 1
class = 50
^
SyntaxError: invalid syntax
3、表达式和语句
- 表达式(expression):值、变量和运算符及其组合;
>>> n
17
>>> 42
42
>>> n + 25
42
- 语句(statement):一个会产生影响的代码单元;
>>> n = 17
>>> print(n)
17
4、脚本模式
- 交互模式(interactive mode) :直接与解释器进行交互;
- 在命令行工具(cmd,Windows PowerShell等,本系列博客均用Windows PowerShell)输入
python
命令启动Python解释器; - 在解释器中输入Python代码以使计算机按代码要求运行并得到需要的结果;
PS C:\Users\Administrator> python
Python 3.8.6 (tags/v3.8.6:db45529, Sep 23 2020, 15:52:53) [MSC v.1927 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> 5
5
>>> x = 5
>>> x + 1
6
- 脚本模式(script mode) :将代码保存为脚本(script)文件,在命令行工具或者各种IDE中启动解释器并调用脚本文件以在解释器运行代码、得到需要的结果,Python脚本文件名的后缀是.py;
- 把代码存为(
D:\WorkSpace\thinkpython2e\
文件夹中的)脚本文件(ex.py
);
5
x = 5
y = x + 1
print(y)
- 在命令行工具(cmd,Windows PowerShell等)输入
python D:\WorkSpace\thinkpython2e\ex.py
命令启动Python解释器并调用脚本文件ex.py以在解释器运行代码、得到需要的结果;
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\ex.py
6
5、运算顺序
- 运算顺序(order of operations):当一个表达式中有多于一个运算符时,计算的顺序;
- 对于算数运算符,运算顺序规则遵循数学里的惯例PEMDAS:
- 括号(Parentheses)具有最高的优先级;
- 指数运算(Exponentiation)具有次高的优先级;
- 乘法(Multiplication)和除法(Division)有相同的优先级, 比加法(Addition)和减法(Subtraction)高,加法和减法也具有相同的优先级;
- 具有相同优先级的运算符按照从左到右的顺序进行计算;
6、字符串运算
- 加号运算符 + :进行将字符串首尾相连起来(字符串拼接(string concatenation))的运算;
- 不能用于数值与字符串运算;
- 乘法运算符 * :进行字符串重复的运算;
- 必须是一个字符串与一个整形数进行运算;
- 字符串与 0 或者负整形数运算的结果为一个空的字符串
''
;
>>> 'Hello' + 'World'
'HelloWorld'
>>> 'Hello' + 3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "int") to str
>>> 'Hello' * 3
'HelloHelloHello'
>>> 'Hello' * -3
''
>>> 'Hello' * 0
''
>>> 'Hello' * 3.5
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't multiply sequence by non-int of type 'float'
7、注释
- 注释(comments):以#符号开始、在程序中用自然语言做的标注;
- 注释可以独立一行,也可以将注释放在行尾;
- 从#开始到行尾的所有内容都会被解释器忽略;
- 注释用于标注代码不明显的特征更有帮助;
v = 5 #速度,单位:米/秒
- 好的变量名能够减少注释,长变量名又使得表达式很难读, 需要平衡;
8、调试
- 程序中可能出现三种错误:语法错误(syntax error)、运行时错误(runtime error)和语义错误(semantic error);
- 语法错误:语法指的是程序的结构及其背后的规则;
- 运行时错误: 即异常(exception),它们的出现通常说明发生了某些特别的(而且不好的)事情;
- 语义错误:与程序的意思的有关,程序在运行时不产生错误信息,不返回正确的结果。
《Think Python 2e》学习精粹(三): 函数
- 函数(function) :一个有命名的、执行某个计算的语句序列(sequence of statements);
1、函数调用
- 函数调用(function call) 方式:函数名(表达式);
- 调用函数时括号里的表达式称为实参(argument);
- 函数“接受”(accept)实参(有的话)后返回(return)得到一个结果即返回值(return value);
>>> type('Hello, World!')
<class 'str'>
- Python提供了能够将值从一种类型转换为另一种类型的内建函数;
- 函数 int 接受任意值,并在其能做到的情况下,将该值转换成一个整型数;
- 函数 float 可以将整型数和字符串转换为浮点数;
- 函数 str 可以将其实参转换成字符串;
>>> int(32.98)
32
>>> int('32')
32
>>> int('32.98')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: '32.98'
>>> float(32)
32.0
>>> float('32.98')
32.98
>>> str(32.98)
'32.98'
2、数学函数
- 模块(module):一个含有相关函数的文件;
- 数学模块(math) 提供了大部分常用的数学函数和变量;
- 在使用模块之前,需要通过 导入语句(import statement) 导入该模块;
- 导入语句生成一个模块对象(module object),在未导入时未生成模块对象;
>>> math
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'math' is not defined
>>> import math
>>> math
<module 'math' (built-in)>
- 模块对象包括了定义在模块内的所有函数和变量;
- 模块对象中的函数或者变量命名:模块的名字与函数名(变量名)组合而成, 并以点号分隔开来, 这种命名形式被称作点标记法(dot notation);
>>> import math
>>> math.pi
3.141592653589793
>>> math.sqrt(2)
1.4142135623730951
3、组合
- 编程语言的最有用特征之一,是能够将小块构建材料(building blocks)组合(compose) 在一起;
- 值可以是一个任意类型的表达式;
- 例外: 赋值语句的左侧必须是一个变量名;
>>> hours = 2
>>> minutes = hours * 60 # 正确
>>> hours * 60 = minutes # 错误!
File "<stdin>", line 1
SyntaxError: cannot assign to operator
4、新建函数
- 函数定义(function definition) :指定新函数的名称、当函数被调用时执行的语句序列;
- def 是表明从此行开始是函数定义的关键字;
- 函数名与变量名具有同样的规则;
- 函数名后面的括号中是实参(有的话),实参不止一个的话用逗号分隔;
- 函数定义的构成规定:
- 函数定义的第一行被称作函数头(header), 其余部分被称作函数体(body);
- 函数头必须以冒号结尾,而函数体必须缩进(惯例四个空格);
- 函数体能包含任意条语句,但必须以空行结束;
- 新建函数调用与内建函数调用一致,包括被应用在另一个函数的内部;
def print_gsf():
print('I am healthy.')
print('And I am happy.')
def print_twice():
print_gsf()
print_gsf()
print_twice()
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\print_twice.py
I am healthy.
And I am happy.
I am healthy.
And I am happy.
5、定义和使用
- 函数定义被执行后,创建函数对象;
- 函数定义内部的语句在函数被调用之前不会执行、不会产生输出;
- 函数定义必须在其第一次被调用之前执行,未定义先调用会报错,例如:
NameError: name 'print_twice' is not defined
;
print_twice()
def print_gsf():
print('I am healthy.')
print('And I am happy.')
def print_twice():
print_gsf()
print_gsf()
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\print_twice_1.py
Traceback (most recent call last):
File "D:\WorkSpace\thinkpython2e\print_twice_1.py", line 1, in <module>
print_twice()
NameError: name 'print_twice' is not defined
6、执行流程
- 执行流程(flow of execution): 语句执行的顺序;
- 执行流程总是从程序的第一条语句开始,自顶向下,每次执行一条语句;
- 函数定义不改变程序执行的流程,函数不被调用时内部的语句不会执行;
- 函数调用,执行流程不进入下一条语句,而是跳入函数体,开始执行函数定义里的语句,然后再回到它离开的位置;
7、形参和实参
- 实参 :调用函数时,(有时候)需要输入参数,输入的一个或者几个参数称为实参;
- 在函数调用时,实参被赋给函数定义时确定的函数形参(parameters) ;
- 组合规则适用于内建函数和自定义函数(programmer-defined functions)的实参;
- 在函数被调用之前,实参会先进行计算再赋给形参;
def print_gsf(years_old):
print(years_old)
def print_twice():
print_gsf(2020-1968)
print_gsf("I'm so young!")
print_twice()
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\print_yeas_old.py
52
I'm so young!
8、变量和形参都是局部的
- 函数定义时创建的变量是局部的(local) , 只在函数内部存在;
- 当函数调用结束时,变量被销毁,在函数外这个变量是未定义的;
9、堆栈图
- 堆栈图(stack diagram) : 用以说明每个变量的值、每个变量所属的函数并跟踪哪个变量能在哪儿用的图形;
- 每个函数用一个栈帧(frame) 表示;
- 一个栈帧就是一个线框,函数名在旁边,形参以及函数内部的变量则在里面;
- 栈帧排列成栈的形式,说明了哪个函数调用了哪个函数等信息;
- __main__是一个表示最上层栈帧的特殊名字;
- 如果函数调用时发生错误,Python会打印出错函数的名字以及调用它的函数的名字, 以及调用后面这个函数的函数的名字,一直追溯到__main__为止,这个函数列表被称作回溯(traceback) ;
- 回溯中的函数顺序,与堆栈图中的函数顺序一致,出错时正在运行的那个函数则位于回溯信息的底部;
10、有返回值函数和无返回值函数
- 有返回值函数(fruitful functions):会返回结果的函数;
- 无返回值函数(void functions):执行一个动作但是不返回任何值的函数;
- 在交互模式下调用一个函数时,Python解释器会马上显示结果;
- 在脚本中,单单调用一个有返回值函数, 返回值就永远丢失;
- 将无返回值函数的结果赋给一个变量:
- 会得到特殊值 None ;
- None 是一个自己有独立类型的特殊值;
>>> print(type(None))
<class 'NoneType'>
11、为什么写函数?
- 给一组语句命名;
- 消除重复的代码;
- 分解长程序;
- 重复使用;
12、调试
- 调试,是最重要的技能之一;
- 调试像是侦探工作:面对一些线索,推理出导致结果的进程(processes)和事件(events);
- 调试也像是一门实验科学:猜测出错的地方,修改程序,再试一次,直至程序正常运行 ;
- 在某种意义上,编程和调试是同一件事,编程是逐步调试一个程序,直到出现期待结果的过程;
- 应该从一个能正常运行(working) 的程序开始,每次只做一些小改动,并同步进行调试。
《Think Python 2e》学习精粹(四): 案例研究—接口设计
- 本章将通过一个案例研究,介绍如何设计出相互配合的函数;
1、turtle 模块
- turtle 模块提供的 turtle.Turtle 函数创建一个 类型为 Turtle 的对象,可以赋值给变量,例如 bob 、sam 或者 jack 等(这里选的都是拟人化的名字);
>>> import turtle
>>> bob = turtle.Turtle()
>>> type(bob)
<class 'turtle.Turtle'>
>>> print(bob)
<turtle.Turtle object at 0x000001C946C106D0>
- Turtle 对象有一系列 method (方法)可供调用:
- bob.fd(100) :向前走100(像素);
- bob.bk(100):向后退100(像素);
- bob.lt(90):左转90(度);
- bob.rt(90):右转90(度);
- bob.pu():抬笔;
- bob.pd():落笔;
>>> import turtle
>>> sam = turtle.Turtle()
>>> sam.fd(100)
>>> sam.lt(90)
>>> sam.fd(100)
>>> sam.lt(90)
>>> sam.fd(100)
>>> sam.lt(90)
>>> sam.fd(100)
>>> turtle.mainloop()
2、简单的重复
- for 语句示例:
for i in range(4):
bob.fd(100)
bob.lt(90)
- for 语句有一个以冒号结尾的语句头(header)以及一个缩进的语句体(body), 语句体可以包含任意条语句;
- for 语句执行流程会贯穿整个语句体,然后再循环回顶部,所以也被称为循环(loop);
- 与自定义函数不同,for 语句的语句体不需要以空行结束;
for i in range(4):
print('Hello, World!')
print('I am gsf.')
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\ex_char4_4.py
Hello, World!
Hello, World!
Hello, World!
Hello, World!
I am gsf.
3、练习
- Turtle 对象可以有多个,这里创建了 bob、 tom、sam 和 jack 等四个 Turtle 对象;
- pu、fd、bk、lt、rt、pu 等方法组合可以使得 Turtle 对象移位;
-
turtle.mainloop()
引入是为了避免Python脚本在命令行执行完毕后,Turtle 对象自动销毁;如果在命令行输入Python语句,则不需要;
import math
import turtle
bob = turtle.Turtle()
tom = turtle.Turtle()
sam = turtle.Turtle()
jack = turtle.Turtle()
# 第二个小题(画可设边长的正方形),先把 Turtle 对象做了移位(下同)
def square(t, length):
for i in range(4):
t.fd(length)
t.lt(90)
sam.pu()
sam.bk(350)
sam.pd()
square(sam,100)
# 第三个小题(画多边形)
def polygon(t, length, n):
for i in range(n):
t.fd(length)
t.lt(360 / n)
tom.pu()
tom.fd(300)
tom.pd()
polygon(tom, 40, 10)
# 第四个小题(画圆)
def circle(t, r):
circumference = 2 * math.pi * r
n = int(circumference / 3) + 1
length = circumference / n
polygon(t, length, n)
circle(bob, 100)
# 第五个小题(画弧线),先把函数 polygon 进行泛化
def polygon_arc(t, length, n, angle):
for i in range(int(n * angle / 360)):
t.fd(length)
t.lt(360 / n)
def arc(t, r, angle):
circumference = 2 * math.pi * r
n = int(circumference / 3) + 1
length = circumference / n
polygon_arc(t, length, n, angle)
jack.pu()
jack.rt(90)
jack.fd(300)
jack.lt(90)
jack.pd()
arc(jack,100,120)
turtle.mainloop()
4、封装
- encapsulation(封装):将一部分代码包装在函数里;
- 封装的好处之一,为这些代码赋予一个名字, 这充当了某种文档说明;
- 另一个好处是,如果重复使用代码, 调用函数更加简洁;
5、泛化
- 泛化(generalization):为函数增加一个形参被称作泛化;
- 下面三个函数是不断泛化的过程:
def square(t):
for i in range(4):
t.fd(100)
t.lt(90)
square(bob)
def square(t, length):
for i in range(4):
t.fd(length)
t.lt(90)
square(bob, 100)
def polygon(t, n, length):
angle = 360 / n
for i in range(n):
t.fd(length)
t.lt(angle)
polygon(bob, 7, 70)
- 泛化使得函数更通用;
- 在实参列表中加入形参的名称,这些被称作关键字实参(keyword arguments);
polygon(bob, n=7, length=70)
- 实参和形参的工作方式: 当调用函数时,实参被赋给形参;
6、接口设计
- 函数的接口(interface) :是一份关于如何使用该函数的总结—形参是什么?函数做什么?返回值是什么?
- 接口让调用者避免处理不必要的细节,直接做自己想做的事,那么这个接口就是“干净的”,如下例中的函数,调用者只须输入圆的半径 r 和 Turtle 对象名称这两个实参;
import math
def circle(t, r):
circumference = 2 * math.pi * r
n = int(circumference / 3) + 1
length = circumference / n
polygon(t, n, length)
- 接口让调用者处理不必要的细节,这个接口就是“不干净的”,如下例中的函数,调用者需输入模拟圆的多边形的边数的实参 n ;
import math
def circle(t, r, n):
circumference = 2 * math.pi * r
length = circumference / n
polygon(t, n, length)
7、重构
- 重构(refactoring) :重新整理一个程序以改进函数接口和促进代码复用的过程;
- 下面第二个函数就是在第一个的基础上重构而得,增加了形参 angle ,促进函数复用;
def polygon(t, length, n):
for i in range(n):
t.fd(length)
t.lt(360 / n)
def polygon_arc(t, length, n, angle):
for i in range(int(n * angle / 360)):
t.fd(length)
t.lt(360 / n)
8、开发方案
- 开发计划(development plan) :是一种编写程序的过程;
- 此例中我们使用的过程是“封装和泛化”:
- 从写一个没有函数定义的小程序开始;
- 一旦该程序运行正常,找出其中相关性强的部分,将它们封装进一个函数并给它一个名字;
- 通过增加适当的形参,泛化该函数。
- 重复1–3步,直到你有一些可正常运行的函数。 复制粘贴有用的代码,避免重复输入(和重新调试);
- 寻找机会通过重构改进程序。 例如,如果在多个地方有相似的代码,考虑将它分解到一个合适的通用函数中;
- 事先不知道如何将程序分解为函数,这是个很有用办法;
9、文档字符串
- 文档字符串(docstring) :位于函数开始位置的一个字符串, 解释了函数的接口;
- 所有的文档字符串都是三重引号(triple-quoted)字符串,也被称为多行字符串;
def polygon_arc(t, n, length, angle):
"""Draws n line segments with the given length and
angle (in degrees) between them. t is a turtle.
"""
for i in range(n):
t.fd(length)
t.lt(angle)
- 简要,包括了他人使用此函数时需要了解的关键信息:
- 说明该函数做什么(不介绍背后的具体细节);
- 解释每个形参对函数的行为有什么影响;
- 每个形参应有的类型 (如果它不明显的话);
- 写文档是接口设计中很重要的一部分;
10、调试
- 函数调用时对实参的要求被称作先决条件(preconditions), 因为它们应当在函数开始执行之前成立(true);
- 函数结束时的条件是后置条件(postconditions):函数预期的效果(如画线段)及任何其他附带效果 (如 Turtle移动、转向或者做其它改变);
- 如果调用者违反一个先决条件,导致函数没有正确工作,则故障(bug)出现在调用者一方,而不是函数;
- 如果满足了先决条件,没有满足后置条件,故障就在函数一方。
《Think Python 2e》学习精粹(五):条件和递归
- 这章的中心话题是能够根据程序的状态执行不同命令的if语句;
1、地板除和求余
- 地板除运算符(floor division operator) //(求模) : 先做除法,然后将结果保留到整数;
- 求余运算符(modulus operator) %(求余) :将两个数相除,返回余数
>>> minutes = 105
>>> hours = minutes // 60
>>> hours
1
>>> remainder = minutes % 60
>>> remainder
45
2、布尔表达式
- 布尔表达式(boolean expression) :用关系运算符进行的运算;
- 关系运算符(relational operators) :共以下六种运算;
x == y #x等于y
x != y # x 不等于 y
x > y # x 大于 y
x < y # x 小于 y
x >= y # x 大于或等于 y
x <= y # x 小于或等于 y
- 布尔表达式(boolean expression)的运算结果要么为真( True)要么为假( False );
- Python 的关系运算符 == 与 赋值运算符 = 所做的运算完全不同、运算结果也迥异;
3、逻辑运算符
- 逻辑运算符(logical operators):and 、or 和 not;
- and :如果两个条件都为真,整个表达式才为真;
- or :如果一个或两个条件为真,那么整个表达式即为真;
- not :运算符对一个布尔表达式取反;
- Python 并不严格要求逻辑运算符的运算数是布尔表达式或者布尔值,任何非0的数字都被解释成为真( True );
>>> 42 and True
True
>>> not 42
False
>>>
4、有条件的执行
- 条件语句(Conditional statements):检测条件、并相应地改变程序行为的语句;
- 有条件的执行(if 语句):如果条件为真,则缩进的语句会被执行, 如果不是,则什么也不会发生;
if x > 0:
print('x is positive')
- if 语句和函数定义有相同的结构:一个语句头跟着一个缩进的语句体;
- 语句体中可出现的语句数目没有限制,至少有一个,一条语句都没有的语句体可以加入 pass 语句;
5、二选一执行
- 二选一执行(if… else…语句):有两个可能的选择,由条件决定执行哪一个,如果条件为真,则执行第一部分语句,如果条件为假,则执行第二部分语句 ;
if x % 2 == 0:
print('x is even')
else:
print('x is odd')
6 、链式条件
- 链式条件(chained conditional)(if …elif…else语句):有时有超过两个可能的情况,需要多于两个的分支,按顺序逐个检测条件,如果它们中有一个为真,相应的分支被执行,并且语句结束;
if x < y:
print('x is less than y')
elif x > y:
print('x is greater than y')
else:
print('x and y are equal')
- elif 语句的数目没有限制;
- else 从句并不是必须的,如果有一个 else 从句, 它必须是在最后;
- 即便有不止一个条件为真,也只执行第一个为真的分支;
7、嵌套条件
- 嵌套条件:一个条件嵌到另一个里面;
if x == y:
print('x and y are equal')
else:
if x < y:
print('x is less than y')
else:
print('x is greater than y')
- 很难快速地阅读嵌套条件(nested conditionals),尽量避免使用嵌套条件;
- 逻辑运算符通常是一个简化嵌套条件语句的方法;
if 0 < x:
if x < 10:
print('x is a positive single-digit number.')
if 0 < x and x < 10:
print('x is a positive single-digit number.')
if 0 < x < 10:
print('x is a positive single-digit number.')
8、递归
- 递归(recursion):一个函数调用它自己的过程;
- 条件递归:一个函数在有条件执行中调用它自己;
def countdown(n):
if n <= 0:
print('Blastoff!')
else:
print(n)
countdown(n-1)
- return语句:退出,与其他编程语言中的 break 语句类似;
9、递归函数的堆栈图
- 对于一个递归函数,在堆栈上可能同时有多个栈帧;
- n=0的栈底,被称作基础情形(base case),不再进行递归调用、没有更多的栈帧;
10、无限递归
- 无限递归(infinite recursion) :一个递归永不会到达基础情形,永远进行递归调用, 并且程序永远不会终止;
- 一个具有无限递归的程序并非永远不会终止,当达到最大递归深度时,Python会报告一个错误信息;
>>> def recurse():
... recurse()
...
>>> recurse()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in recurse
File "<stdin>", line 2, in recurse
File "<stdin>", line 2, in recurse
[Previous line repeated 996 more times]
RecursionError: maximum recursion depth exceeded
- 遇到了无限递归,检查函数是否有基础情形,并确保基础情形没有继续调用递归;
11、键盘输入
- Python 提供了内建函数 input ,可以暂停程序运行,并等待用户输入,当用户按下回车键,程序恢复执行;
- input 以字符串形式返回用户键入的内容;
>>> name = input('What...is your name?\n')
What...is your name?
Arthur, King of the Britons!
>>> name
Arthur, King of the Britons!
- 提示语最后的
\n
是一个特别的字符,会造成换行; - 将返回值 int 可得到整型数;
>>> prompt = 'What...is the airspeed velocity of an unladen swallow?\n'
>>> speed = input(prompt)
What...is the airspeed velocity of an unladen swallow?
42
>>> speed
'42'
>>> int(speed)
42
12、调试
- 当出现语法错误和运行时错误的时候,错误信息中最有用的部分是:
- 是哪类错误;
- 在哪儿出现;
- 语法错误通常很容易被找到,但空白分隔符错误很棘手,因为空格和制表符是不可见的,而且我们习惯于忽略它们;
- 实际的错误可能发生在比错误信息指向的地方更早的地方,甚至在前一行,语法错误和运行时错误都是如此;
- 错误信息的提示不都是准确的。
《Think Python 2e》学习精粹(六):有返回值的函数
- 空函数(void) :产生某种效果,像打印一个值或是移动乌龟,但是并不产生一个值;
- 有返回值的函数 :调用这样的函数会生成一个值, 通常将其赋值给某个变量或是作为表达式的一部分;
1、返回值
- 返回值:调用一个函数会生成一个值,这个值称为函数的返回值;
- 调用一个有返回值的函数会生成一个值;
- 通常将函数返回值赋值给某个变量或是作为表达式的一部分;
- 空函数,泛泛地来看,它们没有返回值,更准确地说,它们的返回值是 None ;
- 有返回值的函数中,以包含一个表达式的 return 语句结束:“马上从该函数返回,并使用接下来的表达式作为返回值”;
- return 语句中表达式可以是任意复杂的;
- 条件语句的每一个分支内各有一个返回语句会很有用,一旦一条返回语句执行,函数则终止,不再执行后续的语句;
- 出现在某条return语句之后的代码,或者在执行流程永远不会到达之处的代码,被称为死代码(dead code);
- 在一个有返回值的函数中, 最好保证程序执行的每一个流程最终都会碰到一个 return 语句;
- 函数按照流程执行完毕,未碰到一个 return 语句,返回值将是 None;
def absolute_value(x):
if x < 0:
return -x
if x > 0:
return x
>>> absolute_value(0)
None
def compare(x, y):
if x > y:
return 1
elif x < y:
return -1
elif x == y:
return 0
print(compare(10,10))
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\new23.py
0
2、增量式开发
- 增量式开发( incremental development ) :通过每次只增加和测试少量代码,来避免长时间的调试,这种开发方式即为增量式开发( incremental development );
- 从一个能运行的程序开始,并且每次只增加少量改动,无论你何时遇到错误,都能够清楚定位错误的源头;
- 用临时变量存储中间值,这样你就能显示并检查它们;
- 一旦程序正确运行,你要删除一些脚手架代码,或者将多条语句组成复合表达式,但是前提是不会影响程序的可读性;
def hypotenuse1(a,b):
return 0
def hypotenuse2(a, b):
squared = a**2 + b**2
print("squared = ",squared)
return 0
def hypotenuse3(a, b):
squared = a**2 + b**2
c = math.sqrt(squared)
print("c = ",c)
return 0
def hypotenuse(a, b):
squared = a**2 + b**2
c = math.sqrt(squared)
return c
print(hypotenuse1(3,4))
print(hypotenuse2(3,4))
import math
print(hypotenuse3(3,4))
print(hypotenuse(3,4))
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\new24.py
0
squared = 25
0
c = 5.0
0
5.0
- 脚手架代码(scaffolding) :上述练习中,
print("squared = ",squared)
这样的代码对程序的构建很有用,但不是最终产品的一部分,这样的代码即为脚手架代码(scaffolding) ;
3、组合
- 可以从一个函数内部调用另一个函数;
def area(radius):
return math.pi * radius**2
def distance(x1, y1, x2, y2):
dx = x2 - x1
dy = y2 - y1
dsquared = dx**2 + dy**2
result = math.sqrt(dsquared)
return result
def circle_area(xc, yc, xp, yp):
return area(distance(xc, yc, xp, yp))
4、布尔函数
- 函数可以返回布尔值(booleans);
- 布尔函数返回值:True 或者 False(如果无返回值,则为 None );
def is_between(x, y, z) :
if x <= y and y <= z:
return True
else:
return False
print(is_between(5, 6, 4))
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\new25.py
False
5、再谈递归
- 一个递归定义的函数的例子(阶乘函数 factorial):
def factorial(n):
if n == 0:
return 1
else:
recurse = factorial(n-1)
result = n * recurse
return result
- 递归定义类似循环定义,因为定义中包含一个对已经被定义的事物的引用;
- 上面递归定义的函数(阶乘函数)引入实参3时的堆栈图:
6、信仰之跃
- “信仰之跃”:阅读代码,当遇到一个函数调用时,不再去跟踪程序执行流程,而是假设这个函数正确运行并返回了正确的结果;
- 当遇到递归调用时, 不用顺着执行流程,应该假设每次递归调用能够正确工作(返回正确的结果);
- 当然,在没写完函数的时就假设函数正确工作有一点儿奇怪, 但这也是为什么这被称作信仰之跃了;
7、再举一例
- 另一个递归定义的函数(斐波那契数列 fibonacci ):
def fibonacci (n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fibonacci(n-1) + fibonacci(n-2)
- 如果试图跟踪执行流程,即使是相当小的 n ,也足够头疼的,但遵循信仰之跃这种方法,如果假设这两个递归调用都能正确运行,很明显将他们两个相加就是正确结果;
8、检查类型
- 重构后的阶乘函数,演示了一个有时被称作监护人(guardian) 的模式:
def factorial (n):
if not isinstance(n, int):
print('Factorial is only defined for integers.')
return None
elif n < 0:
print('Factorial is not defined for negative integers.')
return None
elif n == 0:
return 1
else:
return n * factorial(n-1)
- 前两个条件扮演监护人的角色,避免接下来的代码使用引发错误的值;
- 监护人使得验证代码的正确性成为可能;
9、调试
- 将一个大程序分解为较小的函数为调试生成了自然的检查点, 如果一个函数不如预期的运行,有三个可能性需要考虑:
- 该函数获得的实参有些问题,违反先决条件;
- 该函数有些问题,违反后置条件;
- 返回值或者它的使用方法有问题;
- 为了排除第一种可能,你以在函数的开始增加一条 print 语句来打印形参的值(也可以是它们的类型), 或者你可以写代码来显示地检查先决条件;
- 如果形参看起来没问题,就在每个 return 语句之前增加一条 print 语句,来打印返回值;
- 如果可能,手工检查结果;
- 考虑用一些容易检查的值来调用该函数;
- 如果该函数看起来正常工作,则检查函数调用,确保返回值被正确的使用(或者的确被使用了!);
- 在一个函数的开始和结尾处增加打印语句,可以使执行流程更明显;
def factorial(n):
space = ' ' * (4 * n)
print(space, 'factorial', n)
if n == 0:
print(space, 'returning 1')
return 1
else:
recurse = factorial(n-1)
result = n * recurse
print(space, 'returning', result)
return result
factorial(4)
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\new26.py
factorial 4
factorial 3
factorial 2
factorial 1
factorial 0
returning 1
returning 1
returning 2
returning 6
returning 24
《Think Python 2e》学习精粹(七): 迭代
- 迭代:即重复运行某个代码块,有利用递归进行迭代、利用 for 循环进行迭代、利用 while 语句实现迭代等迭代方式;
1、重新赋值
- 对同一变量进行多次赋值是合法的,新的赋值会使得已有的变量指向新的值(同时不再指向旧的值);
>>> x = 5
>>> x
5
>>> x = 7
>>> x
7
- 给变量重新赋值非常有用,但是需要小心使用,对变量频繁重新赋值会使代码难于阅读,不易调试;
2、更新变量
- 重新赋值的一个常见方式是 更新 (update),更新操作中变量的新值会取决于旧值;
>>> x = x + 1
- 如果试图去更新一个不存在的变量,则会返回一个错误,这是因为 Python 是先求式子右边的值,然后再把所求的值赋给 x ;
>>> x = x + 1
NameError: name 'x' is not defined
- 在更新变量之前,须先 初始化(initialize)这个变量,通常是通过一个简单的赋值实现;
>>> x = 0
>>> x = x + 1
- 通过加1来更新变量叫做 递增(increment),减1叫做 递减(decrement);
3、while 语句
- while 语句的执行流程如下,这种形式的流程叫做循环(loop):
- 首先判断条件为真还是为假;
- 如果为假,退出 while 语句,然后执行接下来的语句;
- 如果条件为真,则运行 while 语句体,运行完再返回第一步;
- 循环主体应该改变一个或多个变量的值,这样的话才能让条件判断最终变为假, 从而终止循环;
4、break
- 有些时候循环执行到一半才知道循环该结束了这种情况下,可以使用 break 语句来跳出循环;
while True:
line = input('> ')
if line == 'done':
break
print(line)
print('Done!')
5、平方根
- 循环常用于计算数值的程序中,这类程序一般从一个大概的值开始,然后迭代式地进行改进;
- 牛顿法 (Newton’s method) 是计算平方根的一种方法,想求 a 的平方根,从任意一个估算值 x 开始,利用下面的公式计算出更为较为精确的估算值:
while True:
print(x)
y = (x + a/x) / 2
if y == x:
break
x = y
- 检查两个浮点数是否相等比较危险,与其检查两个浮点数是否完全相等,计算二者之差的绝对值或数量级更为安全;
if abs(y-x) < epsilon: # epsilon 为决定精确度的值, 如 0.0000001
break
6、算法
- 算法:牛顿法就是一个 算法 (Algorithm)示例,是解决一类问题的计算机制 (牛顿法是计算平方根);
- 算法是一个机械的过程,每一步都是依据一组简单的规则跟着上一步来执行的;
- 执行算法的过程是很乏味的,但是设计算法就比较有趣了,是计算机科学的核心;
- 理解自然语言等一些人类下意识能做的事情,目前还没有算法;
7、调试
- “对分调试”法:
- 将问题拆为两半;
- 在代码中间部分或者附近的地方即中间点,添加打印可以检查的值的 print 语句(或其他具有可验证效果的代码),然后运行程序;
- 如果中间点检查出错了,那么就说明程序的前半部分存在问题,如果没问题,则说明是后半部分出错了;
- 每次这样检查,就可以将需要搜索的代码行数减少一半,100行的程序经过6步之后,理论上将找到出错的代码行;
- 确定程序的 “中间部分” :
- 计算行数并且取其中间行是没有意义的;
- 多考虑程序中哪些地方比较容易出问题,或者哪些地方比较容易进行检查;
- 选定一个检查点(断点),这个断点前后出现bug的概率差不多。