本章将讲述运算、变量、选择结构和循环结构。常见的高级语言都提供这些语法。利用这些语法,我们也能利用计算机实现一些小型的程序,从而让编程立即应用于生活。例如,平时做数学运算,可以习惯性地用Python的命令行做计算器。敲几行程序,实现一个小功能,就已经能让人享受编程的乐趣了。

2.1 计算机会算术

1.数值运算

既然名为“计算机”,那么数学计算自然是计算机的基本功。Python中的运算功能简单且符合直觉。打开Python命令行,输入如下的数值运算,立刻就能进行运算:


>>>1 + 9            # 加法。结果为10
>>>1.3 – 4          # 减法。结果为-2.7
>>>3*5              # 乘法。结果为15
>>>4.5/1.5          # 除法。结果为3.0
>>>3**2             # 乘方,即求3的二次方。结果为9    
>>>10%3             # 求余数,就求10除以3的余数。结果为1

有了这些基础运算后,我们就可以像用一个计算器一样使用Python。以买房为例。一套房产的价格为86万元,购买时需要付15%的税,此外还要向银行支付20%的首付。那么我们可以用下面代码计算出需要准备的现金:

广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元


>>>860000*(0.15 + 0.2)  # 结果为301000.0,即30万1千元

除了常见的数值运算,字符串也能进行加法运算。其效果是把两个字符串连成一个字符串:


>>>"Vamei say:" + "Hello World"  # 连接成"Vamei say:Hello World!"

一个字符串还能和一个整数进行乘法运算:


>>>"Vamei"*2                      # 结果为"VameiVamei"

一个字符串与一个整数n相乘的话,会把该字符串重复n次。

2.逻辑运算

除了进行数值运算外,计算机还能进行逻辑运算。如果玩过杀人游戏,或者喜欢侦探小说,那么就很容易理解逻辑。就好像侦探福尔摩斯一样,我们用逻辑去判断一个说法的真假。一个假设性的说法被称为命题,比如说“玩家甲是杀手”。逻辑的任务就是找出命题的真假。

第1章中已经提到,计算机采用了二进制,即用0和1来记录数据。计算机之所以采用二进制,是有技术上的原因。许多组成计算机的原件,都只能表达两个状态,比如电路的开和关、或者电压的高和低。这样造出的系统也相对稳定。如果使用十进制,那么某些计算机原件就要有10个状态,比如把电压分成十个档。那样的话,系统就会变得复杂且容易出错。在二进制体系下,可以用1和0来代表“真”和“假”两种状态。在Python中,我们使用True和False两个关键字来表示真假。True和False这样的数据被称为布尔值(Boolean)。

有的时候,我们需要进一步的逻辑运算,从而判断复杂命题的真假。比如第一轮时我知道了“玩家甲不是杀手”为真,第二轮我知道了“玩家乙不是杀手”也是真。那么在第三轮时,如果有人说“玩家甲不是杀手,而且玩家乙也不是杀手”,那么这个人就是在说真话。用“而且”连接起来的两个命题分别为真,那么整体命题就是真。无形中,我们进行了一次“与”的逻辑运算。在“与”运算中,两个子命题必须都为真时,用“与”连接起来的复合命题才是真。“与”运算就像是接连的两座桥,必须两座桥都通畅,才能过河,如图2-1所示。以“中国在亚洲,而且英国也在亚洲”这个命题为例。“英国在亚洲”这个命题是假的,所以整个命题就是假的。在Python中,我们用and来表示“与”的逻辑运算。


>>>True and True    # 结果为True
>>>False and True   # 结果为False
>>>False and False  # 结果为False

阅读 ‧ 电子书库

图2-1 “与”和“或”运算

我们还可以用“或者”把两个命题复合在一起。与咄咄逼人的“而且”关系相比,“或者”显得更加谦逊。比如在“中国在亚洲,或者英国在亚洲”这个说法中,说话的人就给自己留了余地。由于这句话的前一半是对的,所以整个命题就是真的。“或者”就对应了“或”逻辑运算。在“或”运算中,只要有一个命题为真,那么用“或”连接起来的复合命题就是真。“或”运算就像并行跨过河的两座桥,任意一座通畅,就能让行人过河。

Python用or来进行“或”的逻辑运算。


>>>True or True   # 结果为True
>>>True or False  # 结果为True
>>>False or False # 结果为False

最后,还有一种称为非的逻辑运算,其实就是对一个命题求反。比如“甲不是杀手”为真,那么“甲是杀手”这个反命题就是假。Python使用not这个关键字来表示非运算,比如:


>>>not True  # 结果为False

3.判断表达式

上面的逻辑运算看起来似乎只是些生活经验,完全不需要计算机这样的复杂工具。加入判断表达式之后,逻辑运算方能真正显示出它的威力。

判断表达式其实就是用数学形式写出来的命题。比如“1等于1”,写在Python里就是:


>>>1 == 1    # 结果为True

符号==表示了相等的关系。此外,还有其他的判断运算符:


>>>8.0 != 8.0               # !=, 不等于
>>>4 < 5                   # <, 小于
>>>3 <= 3                  # <=, 小于或等于
>>>4 > 5                   # >, 大于
>>>4 >= 0                  # >=, 大于等于

这些判断表达式都很简单。即使不借助Python,也能很快在头脑中得出它们的真假。但如果把数值运算、逻辑运算和判断表达式放在一起,就能体现出计算机的优势了。还是用房贷的例子,房产价格86万元,税率15%,首付20%。假如我手里有40万元的现金。出于税务原因,我还希望自己付的税款低于13万元,那么是否还可以买这套房子?这个问题可以借用Python进行计算。


>>>860000*(0.15 + 0.2) <= 400000 and 860000*0.15 < 130000

答案是True,可以买房!

4.运算优先级

如果一个表达式中出现多个运算符,就要考虑运算优先级的问题。不同的运算符号优先级不同。运算符可以按照优先级先后归为:


乘方:**
乘除:*   /
加减:+   -
判断:==   >>=   <<=
逻辑:!   and   or

如果是相同优先级的运算符,那么Python会按照从左向右的顺序进行运算,比如:


>>>4 + 2 - 1         # 先执行加法,再执行减法。结果为5

如果有优先级高的运算符,Python会打破从左向右的默认次序,先执行优先级高的运算,比如:


>>>4 + 2*2         # 先执行乘法,再执行加法。结果为8

括号会打破运算优先级。如果有括号存在,会先进行括号中的运算:


>>>(4 + 2)*2         # 先执行加法,再执行乘法。结果为12

2.2 计算机记性好

1.变量革命

上面的运算中出现的数据,无论是1和5.2这样的数值,还是True和False这样的布尔值,都会在运算结束后消失。有时,我们想把数据存储到存储器中,以便在后面的程序中重复使用。计算机存储器中的每个存储单元都有一个地址,就像是门牌号。我们可以把数据存入特定门牌号的隔间,然后通过门牌号来提取之前存储的数据。

但用内存地址来为存储的地址建索引,其实并不方便:

  • 内存地址相当冗长,难以记忆。
  • 每个地址对应的存储空间大小固定,难以适应类型多变的数据。
  • 对某个地址进行操作前,并不知道该地址的存储空间是否已经被占用。

随着编程语言的发展,开始有了用变量的方式来存储数据。变量和内存地址类似,也起到了索引数据的功能。新建变量时,计算机在空闲的内存中开辟存储空间,用来存储数据。和内存地址不同的是,根据变量的类型,分配的存储空间会有大小变化。程序员给变量起一个变量名,在程序中作为该变量空间的索引。数据交给变量,然后在需要的时候通过变量的名字来提取数据。比如下面的Python程序:


v = "Vivian"
print(v)     #打印出"Vivian"

在上面的程序中,我们把数值10交给变量v保存,这个过程称为赋值(Assignment)。Python中用等号=来表示赋值。借助赋值,一个变量就建立了。从硬件的角度来看,给变量赋值的过程,就是把数据存入内存的过程。变量就像能装数据的小房间,变量名是门牌号。赋值操作是让某个客人前往该房间。

“让Vivian入住到v字号房。”

在随后的加法运算中,我们通过变量名v取出了变量所包含的数据,然后通过print()打印出来。变量名是从内存中找到对应数据的线索。有了存储功能,计算机就不会犯“失忆症”了。

“v字号房住的是谁?”

“是Vivian呀。”

酒店的房间会有不同的客人入住或离开。变量也是如此。我们可以给一个变量赋其他的值。这样,房间里的客人就变了。


v = "Tom"
print(v)     #打印出"Tom"

在计算机编程时,经常设置许多变量,让每个变量存储不同功能的数据。例如,电脑游戏中可能记录玩家拥有的不同资源的数目,就可以用不同的变量记录不同的资源。


gold  = 100    # 100个金子
wood  = 20     # 20个木材
wheat = 29     # 29个小麦

在游戏过程中,可以根据情况增加或者减少某种资源。例如,玩家选择伐木,就增加5个木材。这个时候,就可以对相应变量执行加5的操作。


wood  = wood + 5
print(wood)     # 打印出25

计算机先执行赋值符号右边的运算。原有的变量值加5,再赋予给同一个变量。在游戏进行的整个过程中,变量wood起到了追踪木材数据的作用。玩家的资源数据被妥善地存储起来。

变量名直接参与运算,这是迈向抽象思维的第一步。在数学上,用符号来代替数值的做法称为代数。今天的很多中学生都会列出代数方程,来解决“鸡兔同笼”之类的数学问题。但在古代,代数是相当先进的数学。欧洲人从阿拉伯人那里学到了先进的代数,利用代数的符号系统,摆脱了具体数值的桎梏,更加专注于逻辑和符号之间的关系。在代数的基础上发展出近代数学,为近代的科技爆炸打下了基础。变量也让编程有了更高一层的抽象能力。

变量提供的符号化表达方式,是实现代码复用的第一步。比如之前计算购房所需现金的代码:


860000*(0.15 + 0.2)

当我们同时看多套房时,860000这个价格会不断变动。为了方便,我们可以把程序写成:


total       = 860000
requirement = total*(0.15 + 0.2)
print(requirement)         # 打印结果301000.0

这样,每次在使用程序时,只需更改860000这个数值就可以了。当然,我们还会在未来看到更多的复用代码的方式。但变量这种用抽象符号代替具体数值的思维,具有代表性的意义。

2.变量的类型

数据可能有很多不同的类型,例如5这样的整数、5.9这样的浮点数、True和False这样的布尔值,还有第1章中见过的字符串"Hello World!"。在Python中,我们可以把各种类型的数据赋予给同一个变量。比如:


var_integer = 5
print(var_integer)           # a存储的内容为整数5
var_string = "Hello World!"
print(var_string)            # a存储的内容变成字符串"Hello World!"

可以看到,后赋予给变量的值替换了变量原来的值。Python能自由改变变量类型的特征被称为动态类型(Dynamic Typing)。并不是所有的语言都支持动态类型。在静态类型(Static Typing)的语言中,变量有事先说明好的类型。特定类型的数据必须存入特定类型的变量。相比于静态类型,动态类型显得更加灵活便利。

即使是可以自由改变,Python的变量本身还是有类型的。我们可以用type()这一函数来查看变量的类型。比如说:


var_integer = 10
print(type(var_integer))

输出结果是(1)


<class 'int'>

int是整数integer的简写。除此之外,还会有浮点数(Float)、字符串(String,简写为str)、布尔值(Boolean,简写为bool)。常见的类型包括:


>>>a = 100     # 整型
>>>a = 100.0   # 浮点型
>>>a = 'abc'   # 字符串。也可以使用双引号"abc"标记字符串。
>>>a = True    # 布尔值

计算机需要用不同的方式来存储不同的类型。整数可以直接用二进制的数字表示,浮点数却用额外记录小数点的位置。每种数据所需的存储空间也不同。计算机的存储空间以位(bit)为单位,每一位能存储一个0或1的数字。为了记录一个布尔值,我们只需让1代表真值,0代表假值就可以。所以布尔值的存储只需要1位。对于整数4来说,变换成二进制位100。为了存储它,存储空间至少要有3位,分别记录1、0、0。

为了效率和实用性,计算机在内存中必须要分类型存储。静态类型语言中,新建变量必须说明类型,就是这个道理。动态类型的语言看起来不需要说明类型,但其实是把区分类型的工作交给解释器。当我们更改变量的值时,Python解释器也在努力工作,自动分辨出新数据的类型,再为数据开辟相应类型的内存空间。Python解释器贴心的服务让编程更加方便,但也把计算机的一部分能力用于支持动态类型上。这也是Python的速度不如C语言等静态类型语言的一个原因。

3.序列

Python中一些类型的变量,能像一个容器一样,收纳多个数据。本小节讲的序列(Sequence)和下一小节的词典(Dictionary),都是容器型变量。我们先从序列说起。就好像一列排好队的士兵,序列是有顺序的数据集合。序列包含的一个数据被称为序列的一个元素(element)。序列可以包含一个或多个元素,也可以是完全没有任何元素的空序列。

序列有两种,元组(Tuple)和列表(List)。两者的主要区别在于,一旦建立,元组的各个元素不可再变更,而列表元素可以变更。所以,元组看起来就像一种特殊的表,有固定的数据。因此,有的翻译也把元组称为“定值表”。创建元组和表的方式如下:


>>>example_tuple = (2, 1.3, "love", 5.6, 9, 12, False)   # 一个元组
>>>example_list= [True, 5, "smile"]                      # 一个列表
>>>type(example_tuple)   # 结果为'tuple'
>>>type(example_list)    # 结果为'list'

可以看到,同一个序列可以包含不同类型的元素,这也是Python动态类型的一个体现。还有,序列的元素不仅可以是基本类型的数据,还可以是另外一个序列。


>>>nest_list = [1,[3,4,5]]     # 列表中嵌套另一个列表

由于元组不能改变数据,所以很少会建立一个空的元组。而序列可以增加和修改元素,所以Python程序中经常会建立空表:


>>>empty_list = []              # 空列表

既然序列也用于储存数据,那么我们不免要读取序列中的数据。序列中的元素是有序排列,所以可以根据每个元素中的位置来找到对应元素。序列元素的位置索引称为下标(Index)。Python中序列的下标从0开始,即第一个元素的对应下标为0。这一规定有历史原因在里面,是为了和经典的C语言保持一致。我们尝试引用序列中的元素:


>>>example_tuple[0]    # 结果为2
>>>example_list[2]     # 结果为'smile'
>>>nest_list[1][2]     # 结果为5

表的数据可变更,因此可以对单个元素进行赋值。你可以通过下标,来说明想对哪个元素赋予怎样的值:


>>>example_list[1] = 3.0
>>>example_list           # 列表第二个元素变成3.0

元组一旦建立就不能改变,所以你不能对元组的元素进行上面的赋值操作。

对于序列来说,除了可以用下标来找到单个元素外,还可以通过范围引用的方式,来找到多个元素。范围引用的基本样式是:

序列名[下限:上限:步长]

下限表示起始下标,上限表示结尾下标。在起始下标和结尾下标之间,按照步长的间隔来找到元素。默认的步长为1,也就是下限和上限之间的每1个元素都会出现在结果中。引用的多个元素将成为一个新的序列。下面是一些范围引用的例子。


>>>example_tuple[:5]              # 从小标0到下标4,不包括下标5的元素
>>>example_tuple[2:]              # 从下标2到最后一个元素
>>>example_tuple[0:5:2]           #下标为0,2,4的元素。
>>>sliced = example_tuple[2:0:-1] # 从下标2到下标1
>>>type(sliced)                 # 范围引用的结果还是一个元组

上面都是用元组的例子,表的范围引用效果完全相同。在范围引用的时候,如果写明上限,那么这个上限下标指向的元素将不包括在结果中。

此外,Python还提供了一种尾部引用的语法,用于引用序列尾部的元素:


>>>example_tuple[-1]             # 序列最后一个元素
>>>example_tuple[-3]             # 序列倒数第三个元素
>>>example_tuple[1:-1]           # 序列的第二个到倒数第二个元素

正如example_tuple[1:-1]这个例子,如果是范围引用,那么上限元素将不包含在结果中。

序列的好处是可以有序地储存一组数据。一些数据本身就有有序性,比如银行提供的房贷利率,每年都会上下浮动。这样的一组数据就可以存储在序列中:


interest_tuple = (0.01, 0.02, 0.03, 0.035, 0.05)

4.词典

词典从很多方面都和表类似。它同样是一个可以容纳多个元素的容器。但词典不是以位置来作为索引的。词典允许用自定义的方式来建立数据的索引:


>>>example_dict = {"tom":11, "sam":57,"lily":100}
>>>type(example_dict)         # 结果为'dict'

词典包含有多个元素,每个元素以逗号分隔。词典的元素包含两部分,键(Key)和值(Value)。键是数据的索引,值是数据本身。键和值一一对应。比如上面的例子中,"tom"对应11,"sam"对应57,"lily"对应100。由于键值之间的一一对应关系,所以词典的元素可以通过键来引用。


>>>example_dict["tom"]        # 结果为11

在词典中修改或增添一个元素的值:


>>>example_dict["tom"]   = 30
>>>example_dict["lilei"] = 99
>>>example_dict  #结果为{"tom": 30, "lily": 100, "lilei": 99, "sam": 57}

构建一个新的空的词典:


>>>example_dict = {}
>>>example_dict        # 结果为{}

词典不具备序列那样的连续有序性,所以适于存储结构松散的一组数据。比如首付比例和税率可以存在同一个词典中:


rate = {"premium": 0.2, "tax": 0.15}

在词典的例子中,以及大部分的应用场景中,我们都使用字符串来作为词典的键。但其他类型的数据,如数字和布尔值,也可以作为词典的键值。本书将在后面讲解,究竟哪些数据可以作为词典的键值。

2.3 计算机懂选择

1.if结构

到现在为止,我们看到的Python程序都是指令式的。在程序中,计算机指令都按顺序执行。指令不能跳过,也不能回头重复。最早的程序都是这个样子。例如要让灯光亮十次,就要重复写十行让灯亮的指令。

为了让程序能灵活,早期的编程语言加入了“跳转”的功能。利用跳转指令,我们就能在执行过程中跳到程序中的任意一行指令继续向下执行。例如,想重复执行,就跳到前面已经执行过的某一行。程序员为了方便,频繁地在程序中向前或向后跳转。结果,程序的运行顺序看起来就像交缠在一起的面条,既难读懂,又容易出错。

程序员渐渐发现,其实跳转最主要的功能,就是选择性地执行,或者重复执行某段程序。计算机专家也论证出,只要有了“选择”和“循环”两种语法结果,“跳转”就再无必要。两种结构都能改变程序执行的流程,改变指令运行的次序。编程语言进入到结构化的时代。相对于“跳转”带来的“面条式程序”,结构化的程序变得赏心悦目。在现代编程语言中,“跳转”语法已经被彻底废除。

我们先来看选择结构的一个简单的例子。如果一个房子的售价超过50万,那么交易费率为1%,否则为2%。我们用选择结构来写一个程序。


total = 980000
if total > 500000:
    transaction_rate = 0.01
else:
    transaction_rate = 0.02
print(transaction_rate)                # 打印0.01

在这段程序中,出现了我们没见过的if...else...语句。其实这个语句的功能一读就懂。如果总价超过50万,那么交易费率为1%;否则,交易费率为2%。关键字if和else分别有隶属于它们的一行代码,从属代码的开头会有四个空格的缩进。程序最终会根据if后的条件是否成立,选择是执行if的从属代码,还是执行else的从属代码。总之,if结构在程序中实现了分支。

if和else后面可以跟不止一行的程序:


total = 980000
if total > 500000:             # 该条件成立
    print("总价超过50万")       # 执行这一句的打印
    transaction_rate = 0.01     # 设置费率为0.01
else:                           # else部分不执行
    print("总价不超过50万")
    transaction_rate = 0.02
print(transaction_rate)         # 结果为0.01

可以看到,同属于if或else的代码有四个空格的缩进。关键词if和else就像两个老大,站在行首。老大身旁还有靠后站的小弟。老大只有借着条件赢了,站在其身后的小弟才有机会亮相。最后一行print语句也站在行首,说明它和if、else两位老大平起平坐,不存在隶属关系。程序不需要条件判断,总会执行这一句。

else也并非必需的,我们可以写只有if的程序。比如:


total = 980000
if total > 500000:
    print("总价超过50万")          # 条件成立,执行打印。

没有else,实际上与空的else等价。如果if后的条件不成立,那么计算机什么都不用执行。

2.小弟靠后站

用缩进来表明代码的从属关系,是Python的特色。正如我们在第1章中介绍的,用缩进来标记代码关系的设计源自ABC语言。作为对比,我们可以看看C语言的写法:


if ( i > 0 ) {
     x = 1;
     y = 2; 
}

这个程序的意思是,如果变量i大于0,我们将进行括号中所包括的两个赋值操作。在C语言中,用一个花括号来表示从属于if的代码块。一般程序员也会在C语言中加入缩进,以便区分出指令的从属关系。但缩进并非强制的。下面没有缩进的代码,在C语言中也可以正常执行,与上面程序的运行结果没有任何差别:


if ( i > 0 ) {
x = 1;
y = 2; 
}

在Python中,同样的程序必须要写成如下形式:


if i > 0:
   x = 1
   y = 2

在Python中,去掉了i > 0周围的括号,去除了每个语句句尾的分号,表示块的花括号也消失了。多出来了if ...之后的:(冒号),还有就是x = 1 和 y =2前面有四个空格的缩进。通过缩进,Python识别出这两个语句是隶属于if的。为了区分出隶属关系,Python中的缩进是强制的。下面的程序,将产生完全不同的效果:


if i > 0:
   x = 1
y = 2

这里,从属于if的只有x=1这一句,第二句赋值不再归属于if。无论如何,y都会被赋值为2。

应该说,现在大部分的主流语言,如C、C++、Java、JavaScript,都是用花括号来标记程序块的,缩进也不是强制的。这一语法设计源自于流行一时的C语言。另一方面,尽管缩进不是强制的,但有经验的程序员在用这些语言写程序时,也会加入缩进,以便程序更易读。很多编辑器也有给程序自动加缩进的功能。Python的强制缩进看起来非主流,实际上只是在语法层面上执行了这一惯例,以便程序更好看,也更容易读。这种以四个空格的缩进来表示隶属关系的书写方式,还会在Python的其他语法结构中看到。

3.if的嵌套与elif

再回到选择结构。选择结构让程序摆脱了枯燥的指令式排列。程序的内部可以出现分支一样的结构。根据条件不同,同一个程序可以工作于多变的环境。通过elif语法和嵌套使用if,程序可以有更加丰富多彩的分支方式。

下面一个程序使用了elif结构。根据条件的不同,程序有三个分支:


i = 1
if i > 0:                  # 条件1。由于i为1,这一部分将执行。
    print("positive i")
    i = i + 1
elif i == 0:                #条件2。该部分不执行。
    print("i is 0")
    i = i*10 
else:                       #条件3。该部分不执行。
    print("negative i")
    i = i - 1

这里有三个块,分别由if、elif和else引领。Python先检测if的条件,如果发现if的条件为假,则跳过隶属于if的程序块,检测elif的条件;如果elif的条件还是假,则执行else块。程序根据条件,只执行三个分支中的一个。由于i的值是1,所以最终只有if部分被执行。按照同样的原理,你也可以在if和else之间增加多个elif,从而给程序开出更多的分支。

我们还可以让一个if结构嵌套在另一个if结构中:


i  = 5 
if i > 1:                          # 该条件成立,执行内部的代码
    print("i bigger than 1")
    print("good")
if i > 2:                          #嵌套的if结构,条件同样成立。
    print("i bigger than 2")
    print("even better")

在进行完第一个if判断后,如果条件成立,那么程序依次运行,会遇到第二个if结构。程序将继续根据条件判断并决定是否执行。第二个后面的程序块相对于该if又缩进了四个空格,成为“小弟的小弟”。进一步缩进的程序隶属于内层的if。

总的来说,借着if结构,我们给程序带来了分支。根据条件的不同,程序将走上不同的道路,如图2-2所示。

阅读 ‧ 电子书库

图2-2 if选择结构

2.4 计算机能循环

1.for循环

循环用于重复执行一些程序块,在Python中,循环有for和while两种,我们先来看for循环。

从2.3节的选择结构,我们已经看到了如何用缩进来表示程序块的隶属关系。循环也会用到类似的写法。隶属于循环结构的、需要重复的程序会被缩进,比如:


for a in [3,4.4,"life"]:
    print(a)            # 依次打印列表里的各个元素

这个循环就是每次从列表[3,4.4,"life"] 中取出一个元素,然后将这个元素赋值给a,之后执行隶属于for的程序,也就是调用print()函数,把这个元素打印出来。可以看到,for的一个基本用法是在in后面跟一个序列:


for 元素 in 序列:
    statement

序列中元素的个数决定了循环重复的次数。示例中有3个元素,所以print()会执行3次。也就是说,for循环的重复次数是确定的。for循环会依次从序列中取出元素,赋予给紧跟在for后面的变量,也就是上面示例中的a。因此,尽管执行的语句都相同,但由于数据发生了变化,所以相同的语句在三次执行后的效果也会发生变化。

从序列中取出元素,再赋予给一个变量并在隶属程序中使用,是for循环的一个便利之处。但有的时候,我们只是想简单地重复特定的次数,不想建立序列,那么我们可以使用Python提供的range()函数:


for i in range(5):
    print("Hello World!")      # 打印五次"Hello World!"

程序中的5向range()函数说明了需要重复的次数。因此,隶属于for的程序执行了5次。这里,for循环后面依然有一个变量i,它为每次循环起到了计数的功能:


for i in range(5):
    print(i, "Hello World! ")    # 打印序号和"Hello World!"

可以看到,Python中range()提供的计数也是从0开始的,和表的下标一样。我们还看到print()的新用法,就是在括号中说明多个变量,用逗号分开。函数print()会把它们都打印出来。

我们看一个for循环的实用例子。我们之前用元组记录了房贷的逐年利率:


interest_tuple = (0.01, 0.02, 0.03, 0.035, 0.05)

假如有50万元的房贷,且本金不变,那么每年要还的利息有多少呢?用for循环计算:


total = 500000
for interest in interest_tuple:
    repay = total * interest
    print("每年的利息:", repay)

2.while循环

Python中还有一种循环结构,即while循环。while的用法是:


i = 0
while i < 10:
    print(i)
    i = i + 1                 # 从0打印到9

while后面紧跟着一个条件。如果条件为真,则while会不停地循环执行隶属于它的语句。只有条件为假时,程序才会停止。在while的隶属程序中,我们不断改变参与条件判断的变量i,直到它变成10,以至于还不满足条件而终止循环。这是while循环常见的做法。否则,如果while的条件始终为真,则会变成无限循环。

一旦有了无限循环,程序就会不停地运行下去,直到程序被打断或电脑关机。但有时,无限循环也是有用处的。很多图形程序中就有无限循环,用于检查页面的状态等。如果我们开发一个无限抢票的程序,这样的无限循环听起来也不错。无限循环可以用简单暴力的方法写出来:


while True:
    print("Hello World!")

总之,循环实现了相同代码的重复执行,如图2-3所示。

阅读 ‧ 电子书库

图2-3 循环

3.跳过或终止

循环结构还提供了两个有用的语句,可以在循环结构内部使用,用于跳过或终止循环。


continue    #跳过循环的这一次执行,进行下一次的循环操作
break       # 停止执行整个循环

下面的例子中使用了continue:


for i in range(10):
    if i == 2: 
        continue
    print(i) # 打印0、1、3、4、5、6、7、8、9,注意跳过了2。

当循环执行到i为2的时候,if条件成立,触发continue,不打印此时的i,程序直接进行下一次循环,把3赋值给i,继续执行for的隶属语句。

continue只是跳过某次循环,而break要暴力得多,它会中止整个循环。


for i in range(10):
    if i == 2:
       break
    print(i)  # 只打印0和1

当循环执行到i = 2的时候,if条件成立,触发break,整个循环停止。程序不再执行for循环内部的语句。

附录A 小练习

在本章中,我们学会了运算和变量,还了解了选择、循环两种流程控制结构。现在,让我们做一个复杂些的练习,把学到的东西一起重温一下。

假设我可以全额贷款买房。房子的总价为50万。为了吸引购房者,房贷前四年利率有折扣,分别1%、2%、3%、3.5%。其余的年份里,房贷的年利率都是5%。我逐年还款,每次最多偿还3万元。那么,完全还清房款最少需要多少年?

想一想如何用Python来解决这个问题。如果想清楚了,就可以写程序尝试一下。学习编程的最好方式就是亲自动手,努力解决问题。下面是笔者的解决方案,仅供参考。


i        = 0
residual = 500000.0
interest_tuple= (0.01, 0.02, 0.03, 0.035)
repay = 30000.0
    
while residual > 0:
    i = i + 1
    print("第",i,"年还是要还钱")
    if i<= 4:
    interest = interest_tuple[i - 1] # 序列的下标从0开始
else:
    interest = 0.05
residual = residual * (interest + 1) - repay 
    
print("第",i+1,"年终于还完了")        # 偷偷告诉你,第31年还完

好了,恭喜你还完房贷,也恭喜你学完本章内容。

附录B 代码规范

由于强制缩进的规定,Python代码看起来相对比较整齐。但在一些细节上,如果你能按照特定的规范来写代码,则会让代码看起来更优美。笔者将根据各章的内容,逐步引入相应的代码规范。

Python的官方文档中提供了一套代码规范,即PEP8(2)。PEP是Python改善建议(Python Enhancement Proposal)的简称,包含了Python发展历程中的关键文档。除了PEP8中的规定,笔者还会在下面包括自己写代码的一些小习惯。

1.在下列运算符的前后各保留一个空格:


= + - >  ==  >=  <<= and or not

2.下列运算符的前后不用保留空格:


* / **

3.如果有多行赋值,那么将上下的赋值号=对齐,比如:


num    = 1
secNum = 2

4.变量的所有字母小写,单词之间用下画线连接:


example_number = 10

————————————————————

(1) 如果是Python 2.7,则结果为<type 'int'>。

(2) PEP8文档:http://www.python.org/dev/peps/pep-0008