Python代码结构:条件表达式、循环表达式和推导式

Published on 2017 - 01 - 22

在吉多 · 范 · 罗苏姆开始考虑设计 Python 语言时,他决定通过代码缩进来区分代码块结构,避免输入太多的花括号和关键字。Python 使用空白来区分代码结构,这是初学者需要注意的不同寻常的第一点,而且有其他语言开发经验的人会觉得奇怪。但使用 Python 一段时间后会觉得很自然,而且会习惯于编写简洁的代码来进行大量的编程工作。

使用#注释

注释是程序中会被 Python 解释器忽略的一段文本。通过使用注释,可以解释和明确 Python 代码的功能,记录将来要修改的地方,甚至写下你想写的任何东西。在 Python 中使用 # 字符标记注释,从 # 开始到当前行结束的部分都是注释。你可以把注释作为单独的一行,如下所示:

>>> # 60 sec/min * 60 min/hr * 24 hr/day
>>> seconds_per_day = 86400

也可以把注释和代码放在同一行:

>>> seconds_per_day = 86400 # 60 sec/min * 60 min/hr * 24 hr/day

Python 没有多行注释的符号。你需要明确地在注释部分的每一行开始处加上一个 #。

>>> # 尽管Python不会喜欢,但是我可以在这里讲任何东西
... # 因为我被“保护”
... # 令人敬畏的#号
...
>>>

使用\连接

程序在合理的长度下是易读的。一行程序的(非强制性)最大长度建议为 80 个字符。如果你在该长度下写不完你的代码,可以使用连接符 \(反斜线)。把它放在一行的结束位置,Python 仍然将其解释为同一行。

例如,假设想把一些短字符串拼接为一个长字符串,可以按照下面的步骤:

>>> alphabet = ''
>>> alphabet += 'abcdefg'
>>> alphabet += 'hijklmnop'
>>> alphabet += 'qrstuv'
>>> alphabet += 'wxyz'

或者,使用连接符一步就可以完成:

>>> alphabet = 'abcdefg' + \
...      'hijklmnop' + \
...      'qrstuv' + \
...      'wxyz'

在 Python 表达式占很多行的情况下,行连接符也是必需的:

>>> 1 + 2 +
  File "<stdin>", line 1
    1 + 2 +
          ^
SyntaxError: invalid syntax
>>> 1 + 2 + \
... 3
6
>>>

使用if、elif和else进行比较

到目前为止,我们几乎一直在讨论数据结构。现在,我们将迈出探讨代码结构的第一步,将数据编排成代码。下面第一个例子是一个 Python 小程序,判断一个布尔变量 disaster 的值,然后打印输出合适的取值:

>>> disaster = True
>>> if disaster:
...     print("Woe!")
... else:
...     print("Whee!")
...
Woe!
>>>

程序中,if 和 else 两行是 Python 用来声明判断条件(本例中是 disaster 的值)是否满足的语句。print() 是将字符打印到屏幕的 Python 内建函数。

如果你之前使用过其他编程语言,不需要在 if 判断语句中加上圆括号,例如 if (disaster == True) 是没必要的,但在判断的末尾要加上冒号(:)。如果你像我一样有时会忘记输入冒号,这会导致 Python 解释器报错。

每一个 print() 在判断语句之后要缩进。我一般缩进四个空格。

上面的示例代码做了以下事情,在随后的内容中我还会更详细地介绍。

  • 将布尔值 True 赋值给变量 disaster。
  • 使用 if 和 else 进行条件比较判断,根据 disaster 的值执行不同的代码。
  • 调用 print() 函数,在屏幕打印文本。

同时你可以根据需要进行多层判断语句的嵌套:

>>> furry = True
>>> small = True
>>> if furry:
...     if small:
...         print("It's a cat.")
...     else:
...         print("It's a bear!")
... else:
...     if small:
...         print("It's a skink!")
...     else:
...         print("It's a human. Or a hairless bear.")
...
It's a cat.

在 Python 中,代码缩进决定了 if 和 else 是如何配对的。在第一个判断 furry 中,因为 furry 的值是 True,所以程序跳转到执行判断 if small。我们之前将 small 赋值为 True,所以 if small 的值被估计为 True。因此程序会执行它的下一行,输出 It's a cat。

如果要检查超过两个条件,可以使用 if、elif(即 else if)和 else:

>>> color = "puce"
>>> if color == "red":
...     print("It's a tomato")
... elif color == "green":
...     print("It's a green pepper")
... elif color == "bee purple":
...     print("I don't know what it is, but only bees can see it")
... else:
...     print("I've never heard of the color", color)
...
I've never heard of the color puce

上面的例子中,我们使用了 == 作为判断相等的操作符,Python 中的比较操作符见下表。

相等 ==
不等于 !=
小于 <
不大于 <=
大于 >
不小于 >=
属于 in...

这些操作符都返回布尔值 True 或者 False。让我们看看这些是如何执行的,首先对变量 x

赋值:

>>> x = 7

现在,测试一些例子:

>>> x == 5
False
>>> x == 7
True
>>> 5 < x
True
>>> x < 10
True

如果你想同时进行多重比较判断,可以使用布尔操作符 and、or 或者 not 连接来决定最终表达式的布尔取值。

布尔操作符的优先级没有比较表达式的代码段高,也就是说,表达式要先计算然后再比较。在这个例子中,x 赋值为 7,5 < x 返回 True,x < 10 也同样返回 True, 最终的结果就是 True and True:

>>> 5 < x and x < 10
True

如果对同一个变量做多个 and 比较操作,Python 允许下面的用法:

>>> 5 < x < 10
True

这个表达式和 5 < x and x < 10 是一样的,你也可以使用更长的比较:

>>> 5 < x < 10 < 999
True

什么是真值(True)

如果表达式的返回类型不是布尔会发生什么?什么情况下 Python 会认为是 True 和 False?

一个成假赋值不一定明确表示为 False,下面的情况也会被认为是 False。

布尔 False
null 类型 None
整型 0
浮点型 0.0
空字符串 ''
空列表 []
空元组 ()
空字典 {}
空集合 set()

使用while进行循环

使用 if、elif 和 else 条件判断的例子是自顶向下执行的,但是有时候我们需要重复一些操作——循环。Python 中最简单的循环机制是 while。打开交互式解释器,执行下面的从 1 打印到 5 的简单循环:

>>> count = 1
>>> while count <= 5:
...         print(count)
...         count += 1
...
1
2
3
4
5
>>>

首先将变量 count 的值赋为 1,while 循环比较 count 的值和 5 的大小关系,如果 count 小于等于 5 的话继续执行。在循环内部,打印 count 变量的值,然后使用语句 count += 1 对 count 进行自增操作,返回到循环的开始位置,继续比较 count 和 5 的大小关系。现在,count 变量的值为 2,因此 while 循环内部的代码会被再次执行,count 值变为 3 。

在 count 从 5 自增到 6 之前循环一直进行。然后下次判断时,count <= 5 的条件不满足,while 循环结束。Python 跳到循环下面的代码。

使用break跳出循环

如果你想让循环在某一条件下停止,但是不确定在哪次循环跳出,可以在无限循环中声明 break 语句。这次,我们通过 Python 的 input() 函数从键盘输入一行字符串,然后将字符串首字母转化成大写输出。当输入的一行仅含有字符 q 时,跳出循环 :

>>> while True:
...         stuff = input("String to capitalize[type q to quit]: ")
...         if stuff == "q":
...             break
...         print(stuff.capitalize())
...
String to capitalize[type q to quit]: test
Test
String to capitalize[type q to quit]: hey, it works
Hey, it works
String to capitalize[type q to quit]: q
>>>

使用continue跳到循环开始

有时我们并不想结束整个循环,仅仅想跳到下一轮循环的开始。下面是一个编造的例子:读入一个整数,如果它是奇数则输出它的平方数;如果是偶数则跳过。同样使用 q 来结束循环,代码中加上了适当的注释:

>>> while True:
...         value = input("Integer, please[q to quit]: ")
...         if value == 'q':      # 停止循环
...             break
...         number = int(value)
...         if number % 2 == 0:   # 判断偶数
...             continue
...         print(number, "squared is", number*number)
...
Integer, please[q to quit]: 1
1 squared is 1
Integer, please[q to quit]: 2
Integer, please[q to quit]: 3
3 squared is 9
Integer, please[q to quit]: 4
Integer, please[q to quit]: 5
5 squared is 25
Integer, please[q to quit]: q
>>>

循环外使用else

如果 while 循环正常结束(没有使用 break 跳出),程序将进入到可选的 else 段。当你使用循环来遍历检查某一数据结构时,找到满足条件的解使用 break 跳出;循环结束,即没有找到可行解时,将执行 else 部分代码段:

>>> numbers = [1, 3, 5]
>>> position = 0
>>> while position < len(numbers):
...         number = numbers[position]
...         if number % 2 == 0:
...             print('Found even number', number)
...             break
...         position += 1
...     else: #没有执行break
...         print('No even number found')
...
No even number found

使用for迭代

Python 频繁地使用迭代器。它允许在数据结构长度未知和具体实现未知的情况下遍历整个数据结构,并且支持迭代快速读写中的数据,以及允许不能一次读入计算机内存的数据流的处理。

下面这一遍历序列的方法是可行的:

>>> rabbits = ['Flopsy', 'Mopsy', 'Cottontail', 'Peter']
>>> current = 0
>>> while current < len(rabbits):
...         print(rabbits[current])
...         current += 1
...
Flopsy
Mopsy
Cottontail
Peter

但是,有一种更优雅的、Python 风格的遍历方式:

>>> for rabbit in rabbits:
...         print(rabbit)
...
Flopsy
Mopsy
Cottontail
Peter

列表(例如 rabbits)、字符串、元组、字典、集合等都是 Python 中可迭代的对象。元组或者列表在一次迭代过程产生一项,而字符串迭代会产生一个字符,如下所示:

>>> word = 'cat'
>>> for letter in word:
...         print(letter)
...
c
a
t

对一个字典(或者字典的 keys() 函数)进行迭代将返回字典中的键。在下面的例子中,字典的键为图板游戏 Clue(《妙探寻凶》)中牌的类型:

>>> accusation = {'room': 'ballroom', 'weapon': 'lead pipe',
                  'person': 'Col. Mustard'}
>>> for card in accusation:  # 或者是for card in accusation.keys():
...         print(card)
...
room
weapon
person

如果想对字典的值进行迭代,可以使用字典的 values() 函数:

>>> for value in accusation.values():
...         print(value)
...
ballroom
lead pipe
Col. Mustard

为了以元组的形式返回键值对,可以使用字典的 items() 函数:

>>> for item in accusation.items():
...         print(item)
...
('room', 'ballroom')
('weapon', 'lead pipe')
('person', 'Col. Mustard')

记住,元组只能被初始化一次,它的值是不能改变的。对于调用函数 items() 返回的每一个元组,将第一个返回值(键)赋给 card,第二个返回值(值)赋给 contents:

>>> for card, contents in accusation.items():
...         print('Card', card, 'has the contents', contents)
...
Card weapon has the contents lead pipe
Card person has the contents Col. Mustard
Card room has the contents ballroom

使用break跳出循环

在 for 循环中跳出的用法和在 while 循环中是一样的。

使用continue跳到循环开始

在一个循环中使用 continue 会跳到下一次的迭代开始,这一点和 while 循环也是类似的。

循环外使用else

类似于 while,for 循环也可以使用可选的 else 代码段,用来判断 for 循环是否正常结束(没有调用 break 跳出),否则会执行 else 段。

当你想确认之前的 for 循环是否正常跑完,增加 else 判断是有用的。下面的例子中,for 循环打印输出奶酪的名称,并且如果任一奶酪在商店中找到则跳出循环:

>>> cheeses = []
>>> for cheese in cheeses:
...         print('This shop has some lovely', cheese)
...         break
...     else: # 没有break表示没有找到奶酪
...         print('This is not much of a cheese shop, is it?')
...
This is not much of a cheese shop, is it?

在 for 循环外使用 else 可能和 while 循环一样,不是很直观和容易理解。下面的理解方式会更清楚:for 循环用来遍历查找,如果没有找到则调用执行 else。

使用zip()并行迭代

在使用迭代时,有一个非常方便的技巧:通过 zip() 函数对多个序列进行并行迭代:

>>> days = ['Monday', 'Tuesday', 'Wednesday']
>>> fruits = ['banana', 'orange', 'peach']
>>> drinks = ['coffee', 'tea', 'beer']
>>> desserts = ['tiramisu', 'ice cream', 'pie', 'pudding']
>>> for day, fruit, drink, dessert in zip(days, fruits, drinks, desserts):
...         print(day, ": drink", drink, "- eat", fruit, "- enjoy", dessert)
...
Monday : drink coffee - eat banana - enjoy tiramisu
Tuesday : drink tea - eat orange - enjoy ice cream
Wednesday : drink beer - eat peach - enjoy pie

zip() 函数在最短序列“用完”时就会停止。上面例子中的列表(desserts)是最长的,所以我们无法填充列表,除非人工扩展其他列表。

使用 zip() 函数可以遍历多个序列,在具有相同位移的项之间创建元组。下面创建英语单词和法语单词之间的对应关系的两个元组:

>>> english = 'Monday', 'Tuesday', 'Wednesday'
>>> french = 'Lundi', 'Mardi', 'Mercredi'

现在使用 zip() 函数配对两个元组。函数的返回值既不是元组也不是列表,而是一个整合在一起的可迭代变量:

>>> list( zip(english, french) )
[('Monday', 'Lundi'), ('Tuesday', 'Mardi'), ('Wednesday', 'Mercredi')]

配合 dict() 函数和 zip() 函数的返回值就可以得到一本微型的英法词典:

>>> dict( zip(english, french) )
{'Monday': 'Lundi', 'Tuesday': 'Mardi', 'Wednesday': 'Mercredi'}

使用range()生成自然数序列

range() 函数返回在特定区间的自然数序列,不需要创建和存储复杂的数据结构,例如列表或者元组。这允许在不使用计算机全部内存的情况下创建较大的区间,也不会使你的程序崩溃。

range() 函数的用法类似于使用切片:range(start、stop、step)。而 start 的默认值为 0。唯一要求的参数值是 stop,产生的最后一个数的值是 stop 的前一个,并且 step 的默认值是 1。当然,也可以反向创建自然数序列,这时 step 的值为 -1。

像 zip()、range() 这些函数返回的是一个可迭代的对象,所以可以使用 for ... in 的结构遍历,或者把这个对象转化为一个序列(例如列表)。我们来产生序列 0, 1, 2:

>>> for x in range(0,3):
...         print(x)
...
0
1
2
>>> list( range(0, 3) )
[0, 1, 2]

下面是如何从 2 到 0 反向创建序列:

>>> for x in range(2, -1, -1):
...         print(x)
...
2
1
0
>>> list( range(2, -1, -1) )
[2, 1, 0]

下面的代码片段将 step 赋值为 2,得到从 0 到 10 的偶数:

>>> list( range(0, 11, 2) )
[0, 2, 4, 6, 8, 10]

推导式

推导式是从一个或者多个迭代器快速简洁地创建数据结构的一种方法。它可以将循环和条件判断结合,从而避免语法冗长的代码。会使用推导式有时可以说明你已经超过 Python 初学者的水平。也就是说,使用推导式更像 Python 风格。

列表推导式

你可以从 1 到 5 创建一个整数列表,每次增加一项,如下所示:

>>> number_list = []
>>> number_list.append(1)
>>> number_list.append(2)
>>> number_list.append(3)
>>> number_list.append(4)
>>> number_list.append(5)
>>> number_list
[1, 2, 3, 4, 5]

或者,可以结合 range() 函数使用一个迭代器:

>>> number_list = []
>>> for number in range(1, 6):
...     number_list.append(number)
...
>>> number_list
[1, 2, 3, 4, 5]

或者,直接把 range() 的返回结果放到一个列表中:

>>> number_list = list(range(1, 6))
>>> number_list
[1, 2, 3, 4, 5]

上面这些方法都是可行的 Python 代码,会得到相同的结果。然而,更像 Python 风格的创建列表方式是使用列表推导。最简单的形式如下所示:

[ expression for item in iterable ]

下面的例子将通过列表推导创建一个整数列表:

>>> number_list = [number for number in range(1,6)]
>>> number_list
[1, 2, 3, 4, 5]

在第一行中,第一个 number 变量为列表生成值,也就是说,把循环的结果放在列表 number_list 中。第二个 number 为循环变量。其中第一个 number 可以为表达式,试试下面改编的例子:

>>> number_list = [number-1 for number in range(1,6)]
>>> number_list
[0, 1, 2, 3, 4]

列表推导把循环放在方括号内部。这种例子和之前碰到的不大一样,但却是更为常见的方式。同样,列表推导也可以像下面的例子加上条件表达式:

[expression for item in iterable if condition]

现在,通过推导创建一个在 1 到 5 之间的偶数列表(当 number % 2 为真时,代表奇数;为假时,代表偶数):

>>> a_list = [number for number in range(1,6) if number % 2 == 1]
>>> a_list
[1, 3, 5]

于是,上面的推导要比之前传统的方法更简洁:

>>> a_list = []
>>> for number in range(1,6):
...         if number % 2 == 1:
...             a_list.append(number)
...
>>>  a_list
[1, 3, 5]

最后,正如存在很多嵌套循环一样,在对应的推导中会有多个 for ... 语句。我们先来看一个简单的嵌套循环的例子,并在屏幕上打印出结果:

>>> rows = range(1,4)
>>> cols = range(1,3)
>>> for row in rows:
...         for col in cols:
...             print(row, col)
...
1 1
1 2
2 1
2 2
3 1
3 2

下面使用一次推导,将结果赋值给变量 cells,使它成为元组 (row,col):

>>> rows = range(1,4)
>>> cols = range(1,3)
>>> cells = [(row, col) for row in rows for col in cols]
>>> for cell in cells:
...         print(cell)
...
(1, 1)
(1, 2)
(2, 1)
(2, 2)
(3, 1)
(3, 2)

另外,在对 cells 列表进行迭代时可以通过元组拆封将变量 row 和 col 的值分别取出:

>>> for row, col in cells:
...         print(row, col)
...
1 1
1 2
2 1
2 2
3 1
3 2

其中,在列表推导中 for row ... 和 for col ... 都可以有自己单独的 if 条件判断。

字典推导式

除了列表,字典也有自己的推导式。最简单的例子就像:

{ key_expression : value_expression for expression in iterable }

类似于列表推导,字典推导也有 if 条件判断以及多个 for 循环迭代语句:

>>> word = 'letters'
>>> letter_counts = {letter: word.count(letter) for letter in word}
>>> letter_counts
{'l': 1, 'e': 2, 't': 2, 'r': 1, 's': 1}

程序中,对字符串 'letters' 中出现的字母进行循环,计算出每个字母出现的次数。对于程序执行来说,两次调用 word.count(letter) 浪费时间,因为字符串中 t 和 e 都出现了两次,第一次调用 word.count() 时已经计算得到相应的值。下面的例子会解决这个小问题,更符合 Python 风格:

>>> word = 'letters'
>>> letter_counts = {letter: word.count(letter) for letter in set(word)}
>>> letter_counts
{'t': 2, 'l': 1, 'e': 2, 'r': 1, 's': 1}

字典键的顺序和之前的例子是不同的,因为是对 set(word) 集合进行迭代的,而前面的例子是对 word 字符串迭代。

集合推导式

集合也不例外,同样有推导式。最简单的版本和之前的列表、字典推导类似:

{expression for expression in iterable }

最长的版本(if tests, multiple for clauses)对于集合而言也是可行的:

>>> a_set = {number for number in range(1,6) if number % 3 == 1}
>>> a_set
{1, 4}

参考文档