1.4 程序控制

当声明一个变量时,用表达式来创建与处理对象,如果再添加一些逻辑控制,就形成了语句。也可以说,语句包含了表达式,所以,语句是Python程序构成最基本的一个基础设施。

1.4.1 Python赋值语句

学会 Python 中的赋值语句,其他语句的学习就变得容易了。学习 Python 赋值语句主要是掌握和理解它的赋值逻辑。

1.赋值语句

Python中赋值语句的本质是创建一个对象的引用。主要有以下几种赋值方式。

1)基本赋值方式

“a=5”:就相当于创建了一个变量a,指向内存中存储的5这个对象。“a=5”就是一个赋值语句,执行赋值操作后,可以随意打印或者显示a的值。

2)理解赋值逻辑

通过一个图来演示变量a、对象5在内存中存储的情况。

这里以简单的数值数据为例,以后可能会遇到结果更为复杂的数据,它的存储机制可能会有所区别。

声明变量的时候,最起码有两个部分,一个是用来存储变量名称的变量表,另一个是内存存储区域[4,6]

当声明“a=5”时,首先系统在内存区域开辟了一块存储空间,将数值 5 存储起来,然后将变量 a 指向内存里存储的对象 5,相当于在内存存储区域里先有对象 5,然后再在变量表里出现一个“a”,并且指向 5,这个指向也可以称为“引用”。赋值逻辑的理解如图1-22所示。

图1-22 理解赋值逻辑

图1-22显示了变量的类型与变量的名称无关,即变量本身没有类型约束,在声明变量时不需要声明变量名称的类型,原因在于它的类型取决于它所关联的对象。Python 的变量本身没有类型,即a是没有类型的,它的类型是跟5依附在一起的。

赋值语句的语法虽然简单,但请仔细理解Python中的赋值逻辑。这种赋值逻辑影响着 Python 的方方面面,理解了赋值逻辑,就能更好地理解和编写 Python 程序。如果你有 C 语言的编程经验,便知道在 C 程序中变量是保存了一个值,而在 Python 中的变量是指向一个值。变量只作为一种引用关系存在,而不再拥有存储功能。在Python中,每个数据都会占用一个内存空间,数据在Python中被称为对象(Object)。

一个整数5是一个int型对象,一个'hello'是一个字符串对象,一个[1,2,3]是一个列表对象。

下面来更进一步理解赋值语句。

看下面的例子:

第一个赋值语句表示“a”指向 5 这个对象,第二个语句表示“a”指向“a+5”这个新的数据对象。可以理解为:

Python 把一切数据都看成“对象”。它为每个对象分配一个内存空间。一个对象被创建后,它的id(identity,意为“身份、标识”)就不再发生变化了。

在 Python 中,可以使用全局内置函数 id(obj)来获得一个对象的 id,将其看作该对象在内存中的存储地址。全局内置函数直接使用,不需要引用任何的包。

说明:变量a前后地址的变化说明变量是指向对象的!

一个对象被创建后,它不能被直接销毁。因此,在上面的例子中,变量 a 首先指向了对象 5,然后继续执行“a=a+5”,a+5 产生了一个新的对象 10。由于对象 5 不能被销毁,则令 a 指向新的对象 10,而不是用对象 10 去覆盖对象 5。在代码执行完成后,内存中依然有对象5,也有对象10,只是此时变量a指向了新的对象10。

请大家通过上机测试来理解和掌握赋值逻辑。

如果没有变量指向对象 5(无法引用它),Python 会使用垃圾回收机制来决定是否回收它(这是自动的,不需要程序编写者操心)。

在Python内部有一个垃圾回收机制,垃圾回收机制检测到如果在特定时间内没有变量引用某一个对象,这个对象将被回收,释放它所占用的资源。在Python内部有一个引用计数器,垃圾回收机制根据引用计数器来判断对象是否有引用,以此来决定是否自动释放该对象所占用的资源。垃圾回收机制的目标是Python中未被引用的对象,它是根据引用计数器得到的一个结果来进行推断的。

这一点的理解也很重要,在今后讲到列表等数据类型的时候,我们会再次看到正确理解变量指向对象的重要性。

一个旧的对象不会被覆盖,因旧的对象交互而新产生的数据会放在新的对象中。也就是说每个对象是一个独立的个体,每个对象都有自己的“主权”。因此,两个对象的交互可以产生一个新的对象,而不会对原对象产生影响。在大型程序中,各个对象之间的交互错综复杂,这种独立性使得这些交互足够安全。

接下来考虑另外一个情况——“共享引用”。“共享引用”跟存储有关。先来了解Python的动态特性。

以上两行代码说明了Python的动态特性。接下来介绍Python的“共享引用”。

以上测试结果证明了 y 和 z 都指向了内存中的同一个对象'Tom',可以通过图 1-23来理解“共享引用”。

图1-23 共享引用

所谓“共享引用”指的是多个变量引用同一对象,同一对象通过id检查的内存地址相同。接下来再看一个例子。

注意:判断相等其实有两种意义,一是通过表达式“a==b”判断a和b存储的字面值是否相等,即它们是否都是 50;二是通过调用函数 id 来检查 a 和 b 是否指向了同一对象。

在实际开发过程中,判断两个变量是否指向同一对象,即判断它们的地址是否相同时,除了用函数id来检测,也可以用操作符“is”来判断。如:

“==”判断的是字面值是否相等,而操作符“is”判断的是地址是否相同。请注意加以区分。

以上以逗号隔开完成了对多个变量的赋值操作,它其实就是一个 tuple 元组。后面在学习元组时再来详细介绍它的原理。

4)多目标赋值

多目标赋值是将同一个值赋给多个变量的一种赋值方式。

5)增强赋值或参数化赋值

有时候希望将某个变量的值在它本身原有值的基础上做一个操作之后再重新赋值给它,以替换它原有的值。

【例1-1】 交换两个变量的值。

在其他语言里,比如 Java、C、C++等都至少需要 3 行以上的代码才能完成两个变量值的交换,而在Python中只需要一行代码即可将两个变量的值交换。

在介绍 Python 的其他语句之前,先看一下 Python 流程控制中的顺序执行及基本的输入输出。

顺序执行是流程控制中默认的代码执行方式,比较简单。其基本原理是:代码的执行顺序和程序代码的编写顺序是一致的。

【例1-2】 编程输出一个学生数学、英语、物理3门课程的成绩。

程序执行顺序与编写顺序一致。

以上程序中的数据是在程序中固定的,但有时候可能需要在程序的运行过程中,由编程人员或软件使用人员向程序输入一些数据,这就要用到输入函数。

2.input()函数

控制台上的输入是通过全局函数 input()来实现的,input()函数接收用户从控制台上输入的信息,默认类型为str字符类型,根据需要可以把它转换为特定的数据类型。第2章会详细介绍各种数据类型。

如果想在用户输入数据的时候给用户一些提示,可以在 input()函数中传入一个含提示信息的参数!

【例 1-3】 假设希望在程序运行过程中输入学生的成绩,即将写死的数据改为由控制台操作人员动态输入,则相应的程序可以修改为:

说明:89、95、78三个数值是运行程序后用户从键盘输入的数据。

3.eval()函数

eval()函数将 str 字符型数据当作有效的表达式来求值并返回计算结果。简单来说,就是将字符串左右两端的引号去除。

【例1-4】 在上例的基础上计算3门课程的平均分。

说明:89、98、78三个数值是运行程序后用户从键盘输入的数据。

平均成绩的输出是通过print()函数来完成的。

4.print()函数

这里 print()函数只是简单地输出一个对象或变量的值,事实上 print()函数还有一些常用的参数,在实际开发工作中使用起来非常灵活。

如果希望用一行特殊的字符来对前后行的输出内容进行分隔,如用 20 个“=”来分隔前后两行输出的内容,可以用“print("="*20)”的方式来达成目标。

1)多个变量在一行输出,默认用空格进行分隔

将逗号分隔的多个内容输出在一行上,分隔符是空格。

2)多个变量在一行输出,改变它们之间的分隔符

如果希望输出的内容之间用其他的分隔符进行分隔,则可以写为

其实这里可以用任意字符作为分隔符,即print()函数可以手动指定分隔符。

3)多行print()函数输出在一行上

在 print()函数的参数表中,有一个指定终止符号的参数“end”,默认情况下是换行符,即“end='\n'”。因此,可以通过指定终止符把多个 print 语句的输出内容输出到一行上。

print()函数默认的分隔符是一个空格,终止符号是一个换行符“'\n'”。了解以上内容后,在实际开发中会大大提高输出的灵活性。

5.数字的格式化输出

通过格式化字符串来指定输出数字的格式或位数。

1)输出指定位数的小数

在格式化输出中,花括号里的“:”表示对在当前位置出现的值进行格式化处理,“.2f”表示对后面的值以浮点型来输出,但只保留2位小数,第三位进行四舍五入。

2)输出千位分隔符

如果想让输出的值加上“,”千位分隔符,可以使用如下的形式:

3)输出固定宽度的数值

若想指定整体数字输出的宽度,则形式如下:

说明:“{:12,.2f}”中的“12”表示总的宽度,默认右对齐,不够位数,前面补空格;“,”表示千分位分隔符;“.2f”表示保留2位小数。

因为有时需要在控制台上输出多行内容,每行又有多列,但每行有长有短,若希望排版更整齐一些,此时就可以使用这种方式。

print()函数更多的使用方式,请通过help(print)命令进行查阅。

1.4.2 顺序结构

顺序结构是一个程序中最为简单也最为基本的结构,其执行顺序按照代码的排列顺序自上而下地执行。如交换两个变量的值:

这里,要正确理解语句“a,b=b,a”。系统实际上是把右边的两个变量当作元组(b,a)来进行赋值,具体来说,就相当于执行了以下三步操作:

(1)temp=(b,a),即把元组(b,a)赋值给一个临时变量temp;

(2)a=temp[0],即把temp的第1个元素即b的值取出来赋值给变量a;

(3)b=temp[1],即把temp的第2个元素即a的值取出来赋值给变量b。

以上操作步骤的执行可以在第2章学习了元组数据类型后再来理解。

1.4.3 选择结构

有时候我们希望能根据条件有所选择地执行程序。

选择结构的基本结构可以抽象为:

如果条件成立,则执行语句块1的操作,然后执行if语句之后的操作;如果条件不成立,则执行语句块2的操作,然后再执行if语句之后的操作。

【例1-5】 根据输入的数是否大于0输出不同的提示信息。

运行程序后,如果输入的是数值3,则得到以下的输出结果:

运行程序后,如果输入的是数值-6,则得到的输出结果如下:

1.条件表达式

条件表达式是指由运算符(算术运算符请见表1-1、关系运算符请见表1-2、逻辑运算符请见表1-3)将常量、变量和函数联系起来的有意义的式子。

表1-1 常用的算术运算符

表1-2 关系运算符

表1-3 逻辑运算符

条件表达式只有True或False两个值,因此,条件表达式的值应该为布尔类型。所有值为布尔类型的数据都可以作为条件表达式出现在选择结构中。布尔类型在第 2 章数据类型中会详细介绍。

在此处,大家只需要记住:True的值为1,False的值为0。但是当作为条件来进行判断时,非0即为真(True),0即为假(False)。

说明:0表示假,条件不成立,因此,不执行print()函数,没有信息输出!

2.单分支结构

单分支结构是选择结构中最为简单的一种形式,其中,用冒号(:)表示语句块的开始。其语法格式为:

当条件表达式为真时,则执行语句块1,否则不执行。

3.二分支结构

二分支结构是在单分支结构上,补充当条件表达式不成立时的情况,其语法格式为:

当条件表达式值为True时,执行语句块1;否则,执行语句块2。

由此可见,语句1不会执行,而只会执行条件表达式不成立所对应的语句。Python中二分支结构还有一种更为简洁的语法格式:

作用:如果条件成立,结果为表达式1的值,否则为表达式2的值。

4.多分支结构

当处理多个选择情况时,通过多个if else语句嵌套太过麻烦,Python提供了多分支情况下的处理方式,其基本语法格式为:

其中,elif是else if的缩写。

【例1-6】 某淘宝店的商品在进行打折促销,购买1件商品不打折,购买5件及以上时打8折,购买8件及以上时打7折,购买10件及以上时打5折,购买15件及以上时打3折。每件商品单价3元,假设顾客购买的商品数量通过input()函数输入,编程计算该顾客所需支付的总价。

运行以上程序后,根据输入数据的不同会得到不同的结果,如上。更多情况请自行测试。

1.4.4 循环结构

顺序结构和选择结构已经可以解决大多数问题,但是当需要对一大堆数据进行同样的操作时,此时就要用到循环结构来解决这个问题。

1.遍历循环(迭代语句)

Python中最典型的迭代语句是for-in遍历循环,也称为遍历语句。

Python 中的 for-in 遍历循环可以遍历任何序列的项目,如一个列表或一个字符串。第2章会介绍序列类型(包含可变序列和不可变序列)。

遍历循环的语法格式为:

该遍历循环不是计数循环,循环变量依次从迭代对象(遍历结构)中获取元素,对象(结构)中的元素获取完了,循环就结束了。循环的次数由迭代对象中元素的个数来决定。对获取的每个元素都要执行循环体里的操作,除非遇到 break 或 continue 语句。可以在第2章介绍序列后再来看它的更多应用。

【例1-7】 输出1~5的值,代码如下。

代码中,range()函数返回一个range的可迭代对象。第2章将会介绍range()函数。

【例1-8】 理解不是计数循环的含义。对比上一个例子中输出的结果。

上面的程序虽然在循环体中改变了变量 i 的值,但输出结果并没有变化。原因是:该遍历循环执行时变量依次从迭代对象“range(1,6)”中取出元素,第 1 次取第 1 个元素,第2次循环时取第2个元素,……虽然在第1次循环时,循环体中执行“i=i+3”使得i的值为4,其实,是i指向对象4(请参考1.3.1节的内容认真理解这句话);进入第2次循环时,变量i重新获得迭代对象“range(1,6)”的第2个元素2,即变量i指向对象2,所以第2次循环输出的i值还是2,如此继续,直到迭代对象“range(1,6)”中的元素被遍历完,结束循环。关于迭代对象“range(1,6)”更详细的理解请学习第2章。

如果刚从其他编程语言转到 Python,这是最容易出错的地方。请注意理解 Python中的for-in循环叫遍历循环或叫迭代语句的含义,所以,通常称for-in遍历循环或for-in迭代语句,一般不简单地称为 for-in 循环,以避免引起理解上的混淆。第 2 章序列类型学习之后,我们会看到for-in遍历循环的更多用处。

2.while循环

for-in 遍历循环是当遍历完迭代对象后就结束循环,对于它的循环次数实际上是已知的,如:

虽然已知它的循环次数,但此时不能这样来使用:

出错信息表明“len(range(1,6))”是一个整数而不是一个可迭代序列,“for-in”关键字“in”后一定要跟一个可迭代对象。

所以,当循环次数可以确定时,通常会采用 for-in 遍历循环,利用 range 函数来控制循环的次数。当循环次数不确定时,通常采用 while 循环,通过 while 语句的条件表达式来确定循环是否还要继续。

while循环的语法格式如下:

只要条件表达式的值为真,就要执行循环体里的操作,直到条件为假退出循环为止。

【例1-9】 求和。计算sum=1+2+3+…+10。

由于最后一次执行循环体里的操作使得 i 的值大于 10,导致条件不成立,退出了循环,因此,退出循环后输出的i值为11。

3.continue语句与break语句

continue语句与break语句可以用在for-in遍历循环和while循环中,一般与选择结构配合使用,二者的区别在于 continue 语句仅结束本次循环,即跳过本次循环中循环体里尚未执行的操作,但不跳出循环本身。break 语句则是结束整个循环(如果是嵌套的循环,它只跳出最内层的循环)。以下用实例说明二者的区别。

【例1-10】 计算1~5的偶数之和。

以上程序完成了 1~5 偶数相加的操作。当 i 的值为奇数时,条件“i%2!=0”成立,执行continue语句,跳过语句“sum+=i”。因为continue语句的作用是当流程执行到continue语句时就结束本次循环,即不执行“sum+=i”,而直接进入下一次循环。所以,最终求得的是2+4的结果。

将以上程序中的 continue 语句修改为 break 语句,其他代码行都不变,观察输出的结果。

程序执行到i为3时,条件表达式“i%2!=0”的值为真,执行if中的break语句,而break语句的作用是结束整个循环,即退出循环直接执行循环外的print(sum)语句。所以,此时只加了一个数2,得到“sum=2”的结果。

4.for-in-else和while-else结构

for-in遍历循环和while循环语句都存在一个带else分支的扩展用法,语法格式为:

else 分支中的语句块只在循环正常退出的情况下执行,即 for-in 遍历循环中的变量遍历完迭代对象中的所有元素,才执行 else 分支中的语句块。while 循环是由于条件不成立而退出循环的,不是因为 break 或 return(函数返回中用到的保留字)提前退出循环才执行else分支中的语句块。continue语句对else没有影响。

【例1-11】 对比下面的程序,理解带else分支的for-in遍历循环的执行情况。

不论循环体里执行了什么操作,只要循环体里没有执行 break 或 return,退出循环之后都要执行else分支中的语句,所以最后的输出结果如上所示。

【例1-12】 for-in-else结构中,循环体里包含continue语句的执行情况分析。

说明:循环体里虽然有continue语句,但不影响else分支的执行。

【例1-13】 for-in-else结构中,循环体里包含break语句的执行情况分析。

说明:循环体中的 break 语句将会影响 else 分支的执行,只要执行了这个 break 语句,程序的流程就不会执行else分支!

以上实例说明循环语句中 else 分支的语句块只在正常结束循环后才执行,而如果是因为break或return退出的循环,都不会执行else分支中的语句,这可以理解为else分支是对正常结束循环的一种奖励。

5.嵌套循环

无论是 for-in 遍历循环还是 while 循环,其循环体内的语句本身又可以是一个循环语句,这样就构成了嵌套循环。具体的例子在第2章介绍序列数据类型后我们再来分析。

6.Python语法

Python的语法相较于常见的编程语言有一些特殊,因为它是强制缩进的。

语法:强制缩进要求必须是 4 个空格,这在上面的代码中已经看到了。如果有嵌套的代码块,也是通过继续缩进4个空格来实现的。

【例1-14】 理解代码缩进体现代码的逻辑。

注意:代码行的缩进是必需的!

对于初学者,如果想详尽了解 Python 编程规范,可以搜索“Python 增强标准协定PEP8 标准[7]”,它针对代码的编排、文档的编辑及空格的使用,包括注释等都做了非常详细的说明。有些是强制的,有些是非强制的。请大家通过搜索引擎进行搜索。