第三章:

# 第三章 基本控制结构



## 1. 条件控制结构

采用4个空格表示缩进。

if True:

    print ("True")

else:

    print ("False")

选择结构的基本流程如下图所示。*expr*表示判断条件,不需要额外添加小括号,但是必须以冒号结束。 相同缩进数的语句在一起组成一个语句块,表示受该条件影响的语句块。流程如下图所示:

![if control flow](./figure/python-if.png)

下图中演示了while和if的具体执行流程,while在语法结构上与if完全相同,if只判断一次,但while会循环执行。

![while and if exectuion](https://blog.penjee.com/wp-content/uploads/2016/04/while-loop-modulo-even-numbers-python-animation.gif)

- 对于多重选择,python使用elif作为关键字

```python

age = int(input("请输入你家狗狗的年龄: "))

print("")

if age <= 0:

    print("你是在逗我吧!")

elif age == 1:

    print("相当于 14 岁的人。")

elif age == 2:

    print("相当于 22 岁的人。")

elif age > 2:

    human = 22 + (age -2)*5

    print("对应人类年龄: ", human)

```

- 简洁表达,相当于C语言的问号表达式

  `result1 if  expression  else result2`

```python

flag = int(input())  # only one digit

gender = 'Male' if flag%2 else 'Female'

print(gender)  # output Male if flag is an odd number

```

> 与C语言相同,非0整数值表示`True`,0表示`False`

Python中采用*and, or, not*作为“与或非”的关键字。

> 3

## 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。

你可能感兴趣的:(第三章:)