## 2. 循环控制结构
### 2.1 while循环
通过以下两个动画演示,可以非常容易的了解while循环的执行过程。动画来源[Penjee][1]
![](https://blog.penjee.com/wp-content/uploads/2015/03/while-loop-animation-python.gif)
在下面动画中,展现了list中从尾部移除元素pop函数和从尾部添加元素append()函数的使用方法。
![](https://blog.penjee.com/wp-content/uploads/2015/11/loop-over-python-list-animation.gif)
### 2.2 for...in循环
#### 2.2.1 遍历序列
*for...in*循环主要用来遍历一个序列的所有元素
```python
languages = ["C", "C++", "Python"]
for x in languages: #iterate each element in the list 'languages'
print(x)
# => C
# => C++
# => Python
for index,x in enumerate(languages): #give an index for each element
print(index,x)
# => 0 C
# => 1 C++
# => 2 Python
stars = ['**', '*', '***']
#merge multiple list together
for x, a in zip(languages, stars):
print(x,a)
# => C **
# => C++ *
# => Python ***
```
以上示例中的`zip`函数将两个序列的对应元素构成元素对,然后由元素对构成序列:
```python
print(list(zip(languages, stars)))
# => [('C', '**'), ('C++', '*'), ('Python', '***')]
```
#### 2.2.2 遍历字典
对于一个字典,每个元素都有键和值,形成一个pair,调用字典的items函数完成遍历
```python
tinydict = {'name': 'upc','code':1, 'site': 'www.upc.edu.cn'}
for key in tinydict.keys(): #iterate each key of the dict
print(key,tinydict[key])
for key,val in tinydict.items(): #iterate each pair of the dict
print(key,val)
```
#### 2.2.3 range函数
整数数列是常用的表达,常用于与for循环搭配使用。Python中用range([start,] stop[, step])进行创建整数序列, start: 计数从 start 开始。默认是从 0 开始; stop: 计数到 stop 结束,但不包括 stop,不可省略; step:步长,默认为1。range默认产生一个对象,为了展现其生成的内容,下面代码中将结果强制转换为list对象。
```python
print(list(range(10))) # => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(list(range(1, 11))) # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(list(range(0, 30, 5))) # => [0, 5, 10, 15, 20, 25]
print(list(range(0, 10, 3))) # => [0, 3, 6, 9]
print(list(range(10, 0, -1))) # => [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
print(list(range(0))) # => []
print(list(range(1, 0))) # => []
x = 'intelligence'
for i in range(len(x)) :
print(x[i]) #iterate each character in the string ’x'
```
#### 2.2.4 经典算法--洗牌算法
洗牌算法会将一个序列随机打乱,进行重新排列。Knuth-Durstenfeld洗牌算法是一种经典的算法,它保证每个数在每个位置上出现的概率是相同的。
```python
import random
#Knuth-Durstenfeld Shuffle
def shuffle(lis):
for i in range(len(lis) - 1, 0, -1):
p = random.randrange(0, i + 1)
lis[i], lis[p] = lis[p], lis[i]
return lis
```
用如下方法调用该函数:
```python
r = shuffle([1, 2, 2, 3, 3, 4, 5, 10])
print(r) # => [2, 4, 3, 10, 5, 3, 1, 2]
```
`random`是一个随机库,其中`randrange`函数随机产生一个0和i+1之间的一个整数,不包括i+1。
以上示例与库函数random.shuffle实现方式相同:
```python
import random
lst = [1, 2, 2, 3, 3, 4, 5, 10]
random.shuffle(lst)
print(lst) # => [2, 4, 3, 10, 5, 3, 1, 2]
```
### 2.3 循环使用else语句
这是python中非常特殊的一条语句,循环可以与`else`进行结合,在循环条件为 `False` 时执行 `else` 语句块:
```python
count = 0
while count < 5:
print(count, " is less than 5")
count = count + 1
else:
print(count, " is not less than 5")
# => 0 is less than 5
# => 1 is less than 5
# => 2 is less than 5
# => 3 is less than 5
# => 4 is less than 5
# => 5 is not less than 5
```
下面这段代码演示了经典的素数判断算法:
```python
# Program to check if a number is prime or not
import math
num = 407
# To take input from the user
#num = int(input("Enter a number: "))
# prime numbers are greater than 1
if num > 1:
# check for factors
for i in range(2,int(math.sqrt(num)) + 1):
if (num % i) == 0:
print(num,"is not a prime number") # => 407 is not a prime number
print(i,"times",num//i,"is",num) # => 11 times 37 is 407
break
else: #coresponding to ‘for’ loop. Here 'i' is equal to 'num'
print(num,"is a prime number")
# if input number is less than or equal to 1, it is not prime
else:
print(num,"is not a prime number")
```
注意这里的else与for匹配,而不是与if匹配。当*i*等于*num*时,循环正常结束,else语句块被执行。而循环从break处退出时,不会执行else语句块。
## 3. 函数
### 3.1 函数定义和应用
Python中的函数定义非常简单,应使用函数将单一功能进行抽取,不仅利于代码的重复使用,而且利于代码的组织。定义一个函数规则如下:
- 函数代码块以 `def` 关键词开头,后接函数标识符名称和圆括号 **()**。
- 任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。
- 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
- 函数内容以冒号起始,并且缩进。
- `return [表达式]` 结束函数,选择性地返回一个值给调用方。不写`return`,或不带表达式的`return`相当于返回 None。
```python
def gender( flag ):
"return the gender of the flag"
return 'Male' if flag%2 else 'Female'
print(gender(3)) # => Male.
# 计算面积函数
def area(width, height):
return width * height
w,h = 4,5
print(" area =", area(w, h)) # => area = 20
```
由于隐式元组的机制,函数的返回值可以为多个对象。以下程序的返回值的输出效果可以看出元组的存在。
```python
import math
def circle(r):
return 2*math.pi*r, math.pi*r*r
print(circle(3)) # => (18.84955592153876, 28.274333882308138)
```
### 3.2 函数是一级对象
在 Python 中,一切都是对象,而函数是一级对象(first-class)。也就是说,函数与其他数据类型(如 int)处于平等地位。可以将函数赋值给变量,也可以将其作为参数传入其他函数,将它们存储在其他数据结构(如 dicts)中,并将它们作为其他函数的返回值。
#### 3.2.1 函数是对象
由于其他数据类型(如 string、list 和 int)都是对象,那么函数也是 Python 中的对象。我们来看示例函数 foo,它将自己的名称打印出来:
```python
def foo():
print("foo")
```
由于函数是对象,因此我们可以将函数 foo 赋值给任意变量,然后调用该变量。例如,可以将函数赋值给变量 bar:
```python
bar = foo
bar() # => foo
```
语句 bar = foo 将函数 foo 引用的对象赋值给变量 bar。
#### 3.2.2 **数据结构内的函数**
函数和其他对象一样,可以存储在数据结构内部。例如,可以创建 int-function的字典。当 int 是待执行步骤的简写时,这就会派上用场。
```python
def add(x, y):
return x + y
def sub(x, y):
return x - y
def mult(x, y):
return x * y
# store in dictionary
mapping = {1:add, 2:sub, 3:mult}
x = 1
mapping[x](3,5) # => 8
x = 3
mapping[x](3,5) # => 15
x = int(input('Input a number in 1,2,3: '))
mapping[x](3,5)
```
类似地,函数也可以存储在多种其他数据结构中。
#### 3.2.3 把函数作为参数
函数还可以作为其他函数的参数和返回值。接受函数作为输入或返回函数的函数叫做高阶函数,它是函数式编程的重要组成部分。高阶函数具备强大的能力,允许对动作执行抽象,而不只是抽象数值。是更高阶的抽象。例如想对一个项目列表(list of items)执行迭代,并将其顺序打印出来。可以构建如下函数:
```python
def iterate(list_of_items):
for item in list_of_items:
print(item)
```
这是一级抽象,将数值进行抽象。如果想在对列表执行迭代时进行打印以外的其他操作要怎么做呢?
这就是高阶函数存在的意义。可以创建函数 iterate_custom,待执行迭代的列表和要对每个项应用的函数都是 iterate_custom 函数的输入:
```python
def iterate_custom(list_of_items, custom_func):
for item in list_of_items:
custom_func(item)
```
其中参数custom_func是一个函数,在第3行作为函数被调用。这看起来微不足道,但其实非常强大。已经把抽象的级别提高了一层,对行为进行了抽象,使代码具备更强的可重用性。现在,不仅可以在打印列表时调用该函数,还可以对涉及序列迭代的列表执行任意操作。
#### 3.2.4 把函数作为返回值
函数还能被返回,从而使事情变得更加简单。就像在 dict 中存储函数一样,还可以根据输入参数决定适合的函数。例如:
```python
def calculator(opcode):
if opcode == 1:
return add
elif opcode == 2:
return sub
else:
return mult
my_calc = calculator(2) #my calc is a subtractor
my_calc(5, 4) #returns 5 - 4 = 1
my_calc = calculator(9) #my calc is now a multiplier
my_calc(5, 4) #returns 5 x 4 = 20.
```
第14行和第16行根据输入的参数选择了不同的函数`add`和`mult`,因此虽然第15行和第17行的语句完全相同,但是执行的行为是完全不同的。
### 3.2 lambda表达式
Python中提供了lambda 表达式,通常是在需要一个函数,但是又不想专门一个函数的场合下使用,也就是指**匿名函数**。尤其如果这个函数只会使用一次,专门定义一个函数会造成代码污染。在某处就只需要一个能做一件事情的函数,命名无所谓的时候,就可以使用lambda表达式。例如下面的代码:
```python
def sq(x):
return x * x
map(sq, [y for y in range(10)])
```
可以用lambda表达式替换:
```python
map( lambda x: x*x, [y for y in range(10)] )
```
`lambda x: x*x`与函数sq等价,其中关键字`lambda`后面的`x`表示参数,而冒号`:`后面的`x*x`表示返回值,不写`return`关键字。当熟练掌握了lambda表达式后,它的写法比专门定义一个函数更易读。当然,如果函数体过于复杂,不适合使用lambda表达式。
> lambda表达式必须为单行。
因为函数是对象,也可以用如下方式表达:
```python
sq = lambda x: x*x
print(sq(5)) # => 25
val = (lambda x: x*x)(5)
print(val) # => 25
```
第1行相当给匿名的lambda表达式进行了命名,之后按照新名字进行调用。第3行在应该出现函数的位置直接使用了一个lambda表达式,看起来有些不习惯,请仔细体会。
以下代码对于一个字典类型变量`d`按照值进行排序:
```python
d = {'zhao':78,'qian':84,'sun':95,'li':81}
print(sorted(d.items(), key=lambda item: item[1]))
# => [('zhao', 78), ('li', 81), ('qian', 84), ('sun', 95)]
```
`sorted()`函数的参数key接收一个函数,用来确定比较的方式。对于字典而言,每个元素包含键和值两个部分,需要确定按照键排序,还是按照值排序,或者是其他方式。通过使用lambda表达式,指定了按照每个元素的第二部分进行排序,即按照值进行排序。如果将item[1]修改为item[0],将会按照键进行排序。
下面函数提供了一种特殊的使用方式,它按照字典键的长度进行排序,例如len('li')
```python
print(sorted(d.items(), key=lambda item: len(item[0])))
# => [('li', 81), ('sun', 95), ('zhao', 78), ('qian', 84)]
```
### 3.3 链式调用
网上流行一个“价值1个亿”的智能问答系统,能实现以下问答:
```
me: 会吃饭吗?
AI: 会吃饭!
me: 会用Python编程吗?
AI: 会用Python编程!
me: 明天能来吗?
AI: 明天能来!
```
它的实现方式如下:
```python
while True:
print('AI:',input('me: ').replace('吗','').replace('?','!'))
```
这是一种搞笑的实现方式,将输入字符串中的“吗”去掉,并将问号“?”修改成“感叹号”。代码中比较复杂部分是`input('me: ').replace('吗','').replace('?','!')`,用符号`.`将多个函数链接到一起,称为链式调用。可以按如下方式理解:
```python
s1 = input('me: ')
s2 = s1.replace('吗','')
s3 = s2.replace('?','!')
```
也就是说,`input`的函数返回结果是字符串`s1`,然后`s1`调用`replace`函数返回结果是字符串`s2`,最终结果`s3`是再次调用`replace`函数的结果。也就是说,如果一个函数的返回值是一个对象,就可以调用这个对象拥有的函数。每个对象拥有其预先设定的一系列函数,例如`replace`函数是字符串对象的一个函数,它将字符串中出现的第一个参数用第二个参数替换。关于Python面向对象的详细说明,请参考第五章。
示例代码将三个函数链接到一起,形成一条语句,称为链式调用。链式调用的方法比较简洁,省略了中间变量,是一种比较常用的方式。
可以通过`help`函数详细查询对象或函数的详细说明,例如:
```python
help(str) # 运行结果过长,请自行运行并查看
help(str.replace)
''' =>
Help on method_descriptor:
replace(self, old, new, count=-1, /)
Return a copy with all occurrences of substring old replaced by new.
count
Maximum number of occurrences to replace.
-1 (the default value) means replace all occurrences.
If the optional argument count is given, only the first count occurrences are
replaced.
'''
```
## 4. 列表推导式
列表推导式提供了从序列创建列表的简单途径。通常应用程序将一些操作应用于某个序列的每个元素,用其获得的结果作为生成新列表的元素,或者根据确定的判定条件创建子序列。
列表推导式在Python中非常重要,它书写简单,执行效率比普通循环快,是必须掌握的一种方法。
可以将以下代码
```python
lst = []
for i in range(3):
lst.append(i)
```
替换为:
```python
lst = [i for i in range(3)]
```
以上代码中方括号中的部分就是一个列表推导式。
**例题:** 输入一个整数列表,判断每个整数是否为偶数
```python
lst = [2,5,7,8,14]
print([i%2==0 for i in lst]) # => [True, False, False, True, True]
```
其他使用示例:
```python
#新生成列表的每个元素也可以是列表
vec1 = [2, 4, 6]
print([[x, x**2] for x in vec1]) # => [[2, 4], [4, 16], [6, 36]]
#批量去除前导和后置空格
freshfruit = [' banana', ' berry ', 'passion fruit ']
print([x.strip() for x in freshfruit])# => ['banana', 'berry', 'passion fruit']
#nest loop
vec2 = [4, 3, -9]
print([x*y for x in vec1 for y in vec2])
# => [8, 6, -18, 16, 12, -36, 24, 18, -54]
print([x+y for x in vec1 for y in vec2]) # => [6, 5, -7, 8, 7, -5, 10, 9, -3]
print([vec1[i]*vec2[i] for i in range(len(vec1))]) # => [8, 12, -54]
#行列转换
matrix = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
print([[row[i] for row in matrix] for i in range(4)])
# => [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
```
此外,列表推导式的循环后面还可以加判断条件作为过滤器。
**例题:** 输入一个整数列表,把其中的偶数形成一个新的列表
```python
lst = [2,5,7,8,14]
print([i for i in lst if i%2==0]) # => [2, 8, 14]
```
如果将列表推导式的方括号改成小括号,就变成了生成式。二者的区别是列表推导式一次性返回所有元素,但生成式本身是一个迭代器,作为接收迭代器的函数的参数。二者的执行结果相同,但是在执行过程中会有很大不同:(1) 当生成的元素非常多时,列表生成器占用了大量的空间,但生成器每次只返回一个元素,因此占用的空间非常小;(2) 列表生成器比生成器的执行效率高。因此在数据量比较小的时候,建议使用列表生成器,情况相反时,使用生成器。当接收迭代器的函数的参数只有一个时,小括号可以省略。
**例题:** 输入一个整数列表,输出其中偶数的个数
```python
lst = [2,5,7,8,14]
#the parameter of 'sum' is a generator
print(sum(i%2==0 for i in lst)) # => 3
```
上面代码中*sum()* 函数的参数是一个生成器,*sum()* 函数计算一个列表中所有元素的累积和。因为*True* 代表1,*False* 代表0,所以一个布尔类型列表求累积和的时候,结果就是*True* 的个数。这种方法非常常用。
**例题:** 素数判断。对于3以上的奇数n,如果在3和n的平方根之间的所有奇数都不能整除n,则n为素数。
```python
import math
def is_prime(n):
if n==2: return True
if n<2 or n%2==0: return False
return all(n % i for i in range(3, int(math.sqrt(n)) + 1, 2))
```
其中函数*all*的参数为一个序列,序列中所有元素的逻辑值都为*True*时,返回*True*,否则返回*False*。与*all()*函数类似的函数为*any()*函数,参数中序列的任意一个元素的逻辑值为*True*时,返回*True*,否则返回*False*。
示例中函数`all`的参数是一个列表推导式,`range`从3开始,步长为2,因此遍历了合理范围的所有奇数。如果`n`为`i`的倍数,则`n%i`为0,代表False,否则是一个非0的正整数,代表True。当列表推导式中的任意一个`i`是`n`的因子时,就会产生一个False,导致`all`函数的返回结果为False。