第二章:变量、表达式和语句

编程语言最强大的特性之一,是操作变量的能力。变量是指向某个值的名称。

赋值语句

赋值语句(assignment statement)会新建变量,并为这个变量赋值。

>>> message = 'And now for something completely different'
>>> n = 17
>>> pi = 3.141592653589793

这个例子进行了三次赋值。 第一次将一个字符串赋给一个叫message的新变量; 第二次将整型数17赋给n; 第三次将\(\pi\)的(近似)值赋给pi。

在纸上表示变量的一个常见方法,是写下变量名,并用箭头指向变量的值。 这种图被称为状态图(state diagram),因为它展示了每个变量所处的状态(可以把其看成是变量的心理状态)。 图2-1展示了前面例子的结果。

状态图。

图2-1:状态图。

变量名

程序员通常为变量选择有意义的名字—它们可以记录该变量的用途。

变量名可以任意长。它们可以包括字母和数字,但是不能以数字开头。 使用大写字母是合法的,但是根据惯例,变量名只使用小写字母。

下划线_可以出现在变量名中。 它经常用于有多个单词的变量名,例如my_name或者airspeed_of_unladen_swallow

如果你给了变量一个非法的名称,解释器将抛出一个语法错误:

>>> 76trombones = 'big parade'
SyntaxError: invalid syntax
>>> more@ = 1000000
SyntaxError: invalid syntax
>>> class = 'Advanced Theoretical Zymurgy'
SyntaxError: invalid syntax

76trombones是非法的,因为它以数字开头。more@是非法的,因为它包含了一个非法字符@。 但是,class错在哪儿了呢?

原来,class是Python的关键字(keywords)之一。 解释器使用关键字识别程序的结构,它们不能被用作变量名。

Python 3有以下关键词:

False      class      finally    is         return
None       continue   for        lambda     try
True       def        from       nonlocal   while
and        del        global     not        with
as         elif       if         or         yield
assert     else       import     pass
break      except     in         raise

你没有必要背诵上面的关键词。大部分的开发环境会用不同的颜色区别显示关键词;如果你不小心使用关键词作为变量名,你肯定会发现的。

表达式和语句

表达式(expression)是值、变量和运算符的组合。 值自身也被认为是一个表达式,变量也是,因此下面都是合法的表达式:

>>> 42
42
>>> n
17
>>> n + 25
42

当你在提示符后输入表达式时,解释器会 ** 计算(evaluate)**该表达式,这就意味着解释器会求它的值。在上面的例子中,n的值是17,n + 25的值是42。

语句(statement)是一个会产生影响的代码单元,例如新建一个变量或显示某个值。

>>> n = 17
>>> print(n)

第一行是一个赋值语句,将某个值赋给了n。第二行是一个打印语句,在屏幕上显示n的值。

当你输入一个语句后,解释器会执行(execute)这个语句,即按照语句的指令完成操作。一般来说,语句是没有值的。

脚本模式

到目前为止,我们都是在交互模式(interactive mode)下运行Python,即直接与解释器进行交互。交互模式对学习入门很有帮助,但是如果你需要编写很多行代码,使用交互模式就不太方便了。

另一种方法是将代码保存到一个被称为脚本(script)的文件里,然后以脚本模式(script mode)运行解释器并执行脚本。按照惯例,Python脚本文件名的后缀是.py。

如果你知道如何在本地电脑新建并运行脚本,那你可以开始编码了。否则的话,我再次建议使用PythonAnywhere。我在 http://tinyurl.com/thinkpython2e 上贴出了如何以脚本模式运行解释器的指南。

由于Python支持这两种模式,在将代码写入脚本之前,你可以在交互模式下对代码片段进行测试。不过,交互模式和脚本模式之间存在一些差异,可能会让你感到疑惑。

举个例子,如果你把Python当计算器使用,你可能会输入下面这样的代码:

>>> miles = 26.2
>>> miles * 1.61
42.182

第一行将一个值赋给miles,但是并没有产生可见的效果。 第二行是一个表达式,因此解释器计算它并将结果显示出来。 结果告诉我们,一段马拉松大概是42公里。

但是如果你将相同的代码键入一个脚本并且运行它,你得不到任何输出。 在脚本模式下,表达式自身不会产生可见的效果。虽然Python实际上计算了表达式,但是如果你不告诉它要显示结果,它是不会那么做的。

miles = 26.2
print(miles * 1.61)

这个行为开始可能有些令人费解。

一个脚本通常包括一系列语句。 如果有多于一条的语句,那么随着语句逐个执行,解释器会逐一显示计算结果。

例如,以下脚本

print(1)
x = 2
print(x)

产生的输出结果是

1
2

赋值语句不产生输出。

在Python解释器中键入以下的语句,看看他们的结果是否符合你的理解:

5
x = 5
x + 1

现在将同样的语句写入一个脚本中并执行它。输出结果是什么? 修改脚本,将每个表达式变成打印语句,再次运行它。

运算顺序

当一个表达式中有多于一个运算符时,计算的顺序由运算顺序(order of operations)决定。 对于算数运算符,Python遵循数学里的惯例。 缩写PEMDAS有助于帮助大家记住这些规则:

  • 括号(Parentheses)具有最高的优先级,并且可以强制表达式按你希望的顺序计算。 因为在括号中的表达式首先被计算,那么2 * (3-1)的结果是4,(1+1)**(5-2)的结果是8。 你也可以用括号提高表达式的可读性,如写成(minute * 100) / 60,即使这样并不改变运算的结果。
  • 指数运算(Exponentiation)具有次高的优先级,因此1 + 2**3的结果是9而非27, 2 * 3**2的结果是18而非36。
  • 乘法(Multiplication)和除法(Division)有相同的优先级, 比加法(Addition)和减法(Subtraction)高,加法和减法也具有相同的优先级。 因此2*3-1是5而非4,6+4/2是8而非5。
  • 具有相同优先级的运算符按照从左到右的顺序进行计算(除了指数运算)。 因此表达式degrees / 2 * pi中,除法先运算,然后结果被乘以pi。 为了被\(2 \pi\)除,你可以使用括号,或者写成degrees / 2 / pi。

我不会费力去记住这些运算符的优先级规则。如果看完表达式后分不出优先级,我会使用括号使计算顺序变得更明显。

字符串运算

一般来讲,你不能对字符串执行数学运算,即使字符串看起来很像数字, 因此下面这些表达式是非法的:

'2'-'1'    'eggs'/'easy'    'third'*'a charm'

但有两个例外,+ 和 *。

加号运算符 + 可用于 字符串拼接(string concatenation),也就是将字符串首尾相连起来。例如:

>>> first = 'throat'
>>> second = 'warbler'
>>> first + second
throatwarbler

乘法运算符* 也可应用于字符串;它执行重复运算。 例如,'Spam'*3的结果是'SpamSpamSpam'。 如果其中一个运算数是字符串,则另外一个必须是整型数。

+和*的这个用法,类比加法和乘法也讲得通。 就像4*3与4+4+4等价一样, 我们也会期望'Spam'*3'Spam'+'Spam'+'Spam'等价,而事实上的确如此。 另一方面,字符串拼接和重复与整数的加法和乘法也有很大的不同。 你能想出来一个加法具有而字符串拼接不具有的特性么?

注释

随着程序变得越来越大,越来越复杂,它们的可读性也越来越差。 形式语言是稠密的,通常很难在读一段代码后,说出其做什么或者为什么这样做。

因此,在你的程序中用自然语言做笔记,解释程序做什么通常是比较好的办法。 这些标注被称为注释(comments),以#符号开始。

# 计算逝去的时间占一小时的比例
percentage = (minute * 100) / 60

此例中,注释独立一行。你也可以将注释放在行尾:

percentage = (minute * 100) / 60     # 逝去时间占一小时的比例

从#开始到行尾的所有内容都会被解释器忽略—其对程序执行没有影响。

在注释中记录代码不明显的特征,是最有帮助的。 假设读者能够读懂代码做了什么是合理的; 但是解释代码为什么这么做则更有用。

下面这个注释只是重复了代码,没有什么用:

v = 5     # 将5赋值给v

下面的注释包括了代码中没有的有用信息:

v = 5     # 加速度,单位:米/秒

好的变量名能够减少对注释的需求,但是长变量名使得表达式很难读, 因此这里有个平衡问题。

调试

程序中可能会出现下面三种错误:语法错误(syntax error)、运行时错误(runtime error)和语义错误(semantic error)。我们如果能够分辨出三者区别,有助于快速追踪这些错误。

语法错误:

语法指的是程序的结构及其背后的规则。例如,括号必须要成对出现,所以(1 + 2)是合法的,但是8)则是一个 语法错误

如果你的程序中存在一个语法错误,Python会显示一条错误信息,然后退出运行。你无法顺利运行程序。在你编程生涯的头几周里,你可能会花大量时间追踪语法错误。随着你的经验不断积累,犯的语法错误会越来越少,发现错误的速度也会更快。

运行时错误:

第二种错误类型是运行时错误,这么称呼是因为这类错误只有在程序开始运行后才会出现。这类错误也被称为 异常(exception),因为它们的出现通常说明发生了某些特别的(而且不好的)事情。

在前几章提供的简单程序中,你很少会碰到运行时错误,所以你可能需要一段时间才会接触到这种错误。

语义错误:

第三类错误是“语义”错误,即与程序的意思的有关。如果你的程序中有语义错误,程序在运行时不会产生错误信息,但是不会返回正确的结果。它会返回另外的结果。严格来说,它是按照你的指令在运行。

识别语义错误可能是棘手的,因为这需要你反过来思考,通过观察程序的输出来搞清楚它在做什么。

术语表

变量:
变量是指向某个值的名称。
赋值语句:
将某个值赋给变量的语句。
状态图:
变量及其所指的值的图形化表示。
关键字:
关键字是用于解析程序的;你不能使用if、def和while这样的关键词作为变量名。
运算数(operand):
运算符所操作的值之一。
表达式:
变量、运算符和值的组合,代表一个单一的结果。
计算(evaluate):
通过执行运算以简化表达式,从而得出一个单一的值。
语句:
代表一个命令或行为的一段代码。目前为止我们接触的语句有赋值语句和打印语句。
执行:
运行一个语句,并按照语句的指令操作。
交互式模式:
通过在提示符中输入代码,使用Python解释器的一种方式。
脚本模式:
使用Python解释器从脚本中读取代码,并运行脚本的方式。
脚本:
保存在文件中的程序。
运算顺序:
有关多个运算符和运算数时计算顺序的规则。
拼接:
将两个运算数首尾相连。
注释:
程序中提供给其他程序员(任何阅读源代码的人)阅读的信息,对程序的执行没有影响。
语法错误:
使得程序无法进行解析(因此无法进行解释)的错误。
异常:
只有在程序运行时才发现的错误。
语义:
程序中表达的意思。
语义错误:
使得程序偏离程序员原本期望的错误。

练习题

习题 2-1

和上一章一样,我还是要建议大家在学习新特性之后,在交互模式下充分试验,故意犯一些错误,看看到底会出什么问题。

  • 我们已经知道 n = 42 是合法的。那么 42 = n 呢?
  • x = y = 1 又合法吗?
  • 在某些编程语言中,每个语句都是以分号 结束的。如果你在一个Python语句后也以分号结尾,会发生什么?
  • 如果在语句最后带上句号呢?
  • 在数学记法中,你可以将 \(x\)\(y\) 像这样相乘:\(x y\)。如果你在Python中也这么写的话,会发生什么?

习题 2-2

继续练习将Python解释器当做计算器使用:

  1. 半径为\(r\)的球体积是\(\frac{4}{3} \pi r^3\)。 半径为5的球体积是多少?

  2. 假设一本书的零售价是$24.95,但书店有40%的折扣。运费则是第一本$3,以后每本75美分。 购买60本的总价是多少?

  3. 如果我上午6:52离开家, 以放松跑(easy pace)的速度跑1英里(每英里8:15,即每英里耗时8分15秒),再以 节奏跑(tempo)的速度跑3英里(每英里7:12,即每英里耗时7分12秒),之后又以放松跑的速度跑1英里,我什么时候回到家吃早饭?

    译者注:配速(pace)是在马拉松运动的训练中常使用的一个概念,配速是速度的一种,是每公里所需要的时间。配速=时间/距离。Tempo run一般被翻译成「节奏跑」或「乳酸门槛跑」,是指以比10K或5K比赛速度稍慢(每公里大约慢10-15秒)的速度进行训练,或者以平时15K-半程的配速来跑。参考:https://www.zhihu.com/question/22237002

贡献者

  1. 翻译:@bingjin
  2. 校对:@bingjin
  3. 参考:@carfly