# 《Think Python 2e》学习精粹合集(更新至第七章)


文章目录


  • 本书的目标:教你像计算机科学家一样思考;
  • 计算机科学家的思考方式:使用形式语言表示思想,将零件组成系统,观察复杂系统的行为、形成假设并且对预测进行检验;

《Think Python 2e》学习精粹(一): 程序之道

1、什么是程序

  • 程序 :一系列说明如何执行计算(computation)的指令;
  • 不同的编程语言具体细节不同,但是有很多相通的地方:

    • 输入(input);
    • 输出(output);
    • 数学(math):执行基本的数学运算;
    • 有条件执行(conditional execution);
    • 重复(repetition);

2、运行Python

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):展示了每个变量所处的状态;
    # 《Think Python 2e》学习精粹合集(更新至第七章)

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) : 用以说明每个变量的值、每个变量所属的函数并跟踪哪个变量能在哪儿用的图形;
    # 《Think Python 2e》学习精粹合集(更新至第七章)
  • 每个函数用一个栈帧(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()

# 《Think Python 2e》学习精粹合集(更新至第七章)

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()

# 《Think Python 2e》学习精粹合集(更新至第七章)

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、递归函数的堆栈图

  • 对于一个递归函数,在堆栈上可能同时有多个栈帧;
    # 《Think Python 2e》学习精粹合集(更新至第七章)
  • 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时的堆栈图:

# 《Think Python 2e》学习精粹合集(更新至第七章)

6、信仰之跃

  • “信仰之跃”:阅读代码,当遇到一个函数调用时,不再去跟踪程序执行流程,而是假设这个函数正确运行并返回了正确的结果;
  • 当遇到递归调用时, 不用顺着执行流程,应该假设每次递归调用能够正确工作(返回正确的结果);
  • 当然,在没写完函数的时就假设函数正确工作有一点儿奇怪, 但这也是为什么这被称作信仰之跃了;

7、再举一例

  • 另一个递归定义的函数(斐波那契数列 fibonacci ):# 《Think Python 2e》学习精粹合集(更新至第七章)
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 开始,利用下面的公式计算出更为较为精确的估算值:
    # 《Think Python 2e》学习精粹合集(更新至第七章)
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的概率差不多。
上一篇:《Think Python 2e》作业实现(九): 文字游戏


下一篇:12323