python系列教程之基础特性第二篇-控制流程

前言

我们在高中数学就学过,一段程序之所以能够按照我们预想的步骤执行,就是因为有流程控制语句。也就是表示条件判断的if...else语句,表示循环的for循环以及while语句。这一节我们来简单了解一下if...else语句的执行机制,for循环的常见用法以及不那么常见的高级特性,while语句的使用情景。

条件判断

我们经常会碰到一种情况,就是在满足某个条件的时候做A事,反之做B事。 比如某个网站上的某些内容支队vip用户开放,那么在用户访问这些内容的时候,我们需要对用户对身份进行判断。

1
2
3
4
if current_user.is_vip():
# do something
else:
# do something else

根据python的缩进规则,缩进一样ifelse属于同一层级,实际上,在python不会经常出现条件语句嵌套的情况,一般都可以通过elif语句解决。比如我们要对用户的年龄进行判断,根据用户的年龄推荐不同的内容给他:

1
2
3
4
5
6
7
name = current_user.name
if name < 18:
# do A
elif name < 30::
# do B
else:
# do C

值得注意的是,当if语句有一个为真时,其余的语句就不会执行了。在写很多elif的时候尤其要注意这一点,避免程序的逻辑出错。

循环

python中有两种循环语句,一个是for,一个是while,前者是在要对一个数据集合,可迭代对象,或者generator遍历的时候使用,后者只是重复执行某个操作直到执行条件不满足。

while涉及到的东西比较简单,所以先讲while语句。

while

一个简单的例子就是:假设现在有一个活动是,前一百个满足条件的报名者,可以有礼物。那么主要代码是:

1
2
3
4
n = 0
while n < 100:
if current_user.is_valid():
n += 1

虽然上述逻辑也可以用for循环实现,但是用while是更清楚简洁的。

for

第二钟循环语句是for循环,一般当程序有用来迭代的数据集合的时候,会更倾向于使用for而不是while。 比如打印一个List或者tuple中的元素。

1
2
3
4
names = ['zhu', 'hai', 'hao']
# names = ('zhu', 'hai', 'hao')
for n in names:
print(n)

或者我们还利用range()来达到类似于在c/c++for循环的效果:

1
2
3
for(int i = 0; i < 10; i++) {
std::cout<<i
}

即换成python版本是:

1
2
for i in range(10):
print(i)

break和continue

如果想要提前退出循环,用break语句即可。比如在上面遍历List的例子中,如果想要在找到为hai的名字之后就退出循环,就是:

1
2
3
4
5
names = ['zhu', 'hai', 'hao']
for n in names:
if name == 'hai':
break
print(n)

如果想略过其中某些循环,那么用continue即可。比如我们想要打印1~100的偶数,那么就要跳过中间的奇数,也就是:

1
2
3
4
for i in range(100):
if i % 2 is not 0:
continue
print(i)

循环深入

下面的内容会涉及到函数的一些东西,所以如果看不懂的同学可以看完函数之后再回来看。

迭代器

前面讲过了for循环可以遍历list的所有元素,dict的所有key, 还有range对象。实际上还可以遍历字符串的所有字符,文件的每一行等等。

1
2
3
4
5
for i in 'python'':
print(i)

for line in open('a.txt'):
print(line)

实际上所有这些可以用for循环语句遍历的对象都有一个统一的称呼叫做可迭代对象 – iterable objects。所有的可迭代对象都可以返回一个迭代器 – iterator,这个迭代器是一个惰性计算序列,可以作用于next()函数而返回下一个值。for循环实际上做的事情也就是不断的调用next函数以返回下一个值,因此上面的代码也就等同于:

1
2
3
4
5
6
7
8
9
x = 'python'
y = iter(x)
print(next(y))
print(next(y))
print(next(y))
print(next(y))
print(next(y))
print(next(y))
# print(next(y)) here will raise StopIteration exception

list, str本身只是可迭代,而并不是迭代器。上面的iter()函数是python内置的函数,用来创建iterator。可以通过type()函数查看y的类型:

1
2
>>> type(y)
<str_iterator object at 0x1023cd668>

除了用iter函数外,还可以自己创建一个自定义的iterator或者使用itertools中的函数来创建特殊的iterator。从本质上来讲,只要有__next__()函数和__iter__()函数实现的对象就是一个iterator

有的同学会问了,为什么要用迭代器,直接用for迭代相应的list不就好了吗?可以看出,迭代器是一个可以无限长的惰性序列,你可以不断调用next()函数获得下一个值,如果是从list之类转化而来,那么当获取到最后一个元素之后再调用next()函数就会抛出StopIteration的错误。很多时候我们不确定会用到一段序列的多少元素,那么如果本身序列很长但是事后用到的元素又很少,那么对于内存就是一种浪费,所以可以先描述一段序列的生成方法,只有调用next()函数的时候才计算下一个值。

我们可以实现一个能够打印Fibonacci数列的迭代器。

1
2
3
4
5
6
7
8
9
10
11
12
class PrintFib:
def __init__(self):
self.prev = 0
self.cur = 1
def __iter__(self):
return self

def __next__(self):
tmp = self.cur
self.cur += self.prev
self.prev = tmp
return tmp

然后就可以这样使用:

1
2
3
4
5
6
7
8
9
10
11
>>> f = PrintFib()
>>> next(f)
1
>>> next(f)
2
>>> next(f)
3
>>> next(f)
5
>>> next(f)
8

显然,我们不可能把所有的斐波那契数都存放在list中,这个时候用惰性计算就可以获取任意一个斐波那契数。

itertools

python还内置了很多有意思的函数可以通过list创建有特殊用途的迭代器。

比如chain()函数可以将多个迭代器合并成一个迭代器:

1
2
3
4
5
6
7
8
9
10
11
12
13
In [55]: for i in chain('hello', 'world'):
...: print(i)
...:
h
e
l
l
o
w
o
r
l
d

izip()函数可以通过多个输入的迭代器组合对应元素成tuple

1
2
3
4
5
6
In [59]: for i in zip([1, 2, 3], ['a', 'b', 'c'], ['c', 'd', 'e']):
...: print(i)
...:
(1, 'a', 'c')
(2, 'b', 'd')
(3, 'c', 'e')

islice()函数可以截取输入迭代器的一部分作为一个新的迭代器返回:

1
2
3
4
5
6
7
8
In [6]: for i in islice(f,0, 5):
...: print(i)
...:
1
1
2
3
5

更多的函数可以参考官方文档

生成器

通过编写类的方式我们可以自定义迭代器,但是有时候我们的生成序列的逻辑可能很简单,通过编写类的方式实现会有很多无用的代码。因此生成器可能使用起来更加顺手。

生成器有两种,一个是生成器函数,通过yield关键字返回当前值。一种是生成器表达式,当我们的逻辑简单到编写函数都多余的时候,不妨使用生成器表达式使代码更加优雅。

还是以斐波那契序列为例子,这次用生成器函数来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
In [60]: def fib():
...: prev, cur = 0, 1
...: while True:
...: yield cur
...: prev, cur = cur, prev + cur
...:

In [61]: f= fib()

In [62]: next(f)
Out[62]: 1

In [63]: next(f)
Out[63]: 1

In [64]: next(f)
Out[64]: 2

In [65]: next(f)
Out[65]: 3

In [66]: next(f)
Out[66]: 5

In [67]: next(f)
Out[67]: 8

生成器表达式和列表推导相似,只不过将后者的[]换成(),比如我们创建一个生成器包含100以内的平方数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
In [69]: g = (x*x for x in range(10))

In [70]: next(g)
Out[70]: 0

In [71]: next(g)
Out[71]: 1

In [72]: next(g)
Out[72]: 4

In [73]: for i in g:
...: print(i)
...:
9
16
25
36
49
64
81

从上面的代码我们坏可以看出,我们既可以通过next()来获取下一个值,也可以用for循环,而且二者是相通的。

最后

最后我们用一张图来总结上面一些重要概念的关系:

迭代器与生成器

下一篇文章是python中的函数。

有钱的捧个钱场,没钱的捧个人场