vlambda博客
学习文章列表

Python A 学习笔记 第二部分

本笔记全部代码都是在Python3.8环境下运行的


第四章 程序的控制结构

4.1 程序的基本结构

4.1.1 程序流程图

程序流程图用一系列图形、流程线和文字说明描述程序的基本操作和控制流程,它是程序分析和过程描述的最基本方式。流程图的基本元素包括7种,如下图所示:

起止框表示程序的开始和结束;判断框判断条件是否成立并选择不同的执行路径;处理框表示一组处理过程;输入/输出框表示数据的输入或输出;注释框用于增加程序的解释;流向线表示程序的执行路径。连接点将多个流程图连接到一起,例如:

Python A 学习笔记 第二部分

4.1.2 程序的基本结构

计算机程序可以看作一条一条顺序执行的代码。顺序结构是程序的基础,但是单一的顺序结构不能解决所有的问题,因此需要引入控制结构来更改程序执行顺序以满足多种功能需求。

程序由3种基本结构组成:

  1. 顺序结构;

  2. 分支结构;

  3. 循环结构;

上述结构都有一个入口和一个出口。任何程序都是由这三种基本结构组合而成。

分支结构是程序根条件判断结果而选择不同执行路径的一种运行方式。分支结构有单分支和双分支这两种结构:

·Python A 学习笔记 第二部分

循环结构是程序根据条件判断结果向后反复执行的一种运行方式。循环结构有条件循环和遍历循环结构,也就是一个根据条件,一个根据已知次数。

Python A 学习笔记 第二部分


4.2 程序的分支结构

4.2.1 单分支结构 if语句

在Python中的if语句的语法格式如下:

if <条件>: <语句块>

语句的控制过程如图所示:

Python A 学习笔记 第二部分

if语句中的语句块执行与否依赖于条件的判断,但是无论在什么情况下,控制都会转移到if语句后与该语句同级别的下一条语句。语句条件部分可以使用任何能够产生TrueFalse的语句和函数。Python 语言中有6个关系操作符:

Python A 学习笔记 第二部分

4.2.2 二分支结构 if-else语句

语法格式如下:

if < 条件 >: < 语句块 1 >else: < 语句块 2 >

语句块1是在if条件满足之后执行的一个或多个语句序列,语句块2是在if条件不满足后执行的语句。

4.2.3 多分支结构 if-elif-else语句

在Python中的语句格式如下:

if < 条件1 >: < 语句块1 >elif < 条件2 >: < 语句块2 >...else: < 语句块N >

其控制流程图如下所示:

Python A 学习笔记 第二部分

多分支结构是二分支结构的扩展,这种形式通常用于设置同一个判断条件的多条执行路径。Python会依次寻找第一个结果为True的条件,结束后跳过整个if-elif-else结构,执行后面的语句。如果没有任何条件成立,else下面的语句块将被执行。


4.3 程序的循环结构

根据循环执行次数的确定性,循环结构可以分为确定次数循环和非确定次数循环。前者被称为”遍历循环“。

4.3.1 遍历循环 for语句

在Python中通过保留字for在实现”遍历循环“,其基本语法结构如下:

for < 循环变量 > in < 遍历结构 >: < 语句块 >

遍历循环可理解为:从遍历结构中逐一提取元素,放在循环变量中,对于每个提取的元素执行一次语句块。

遍历结构可以是”字符串、文件、组合数据类型或range()函数等,常用的方式如下:

循环N次:for i in range(N): < 语句块 >
遍历文件fi的每一行for line in fi: < 语句块 > 遍历字符串s:for c in s: < 语句块 >
遍历列表ls:for item in ls: < 语句块 >

4.3.2 无限循环 while语句

无限循环一直保持循环操作直到循环条件不满足才结束,不需要提前确定循环次数。Python中的基本使用方法如下:

while < 条件 >: < 语句块 >

其中条件与if语句中的判断条件一样,结果为TrueFalse

4.3.3 循环保留字 breakcontinue

这两个保留字用于辅助控制循环执行。

break用于跳出最内层forwhile循环,脱离该循环后程序从循环代码后继续执行:

for s in "BIT": for i in range(10): print(s,end="") if s == "I": break

Python A 学习笔记 第二部分

其中,break语句跳出了最内层for循环,但仍然继续执行外层循环,该语句只有能力跳出当前层次循环。

continue用来结束当前当次循环,即跳出循环体中下面尚未执行的语句,但不跳出当前循环。

for s in "PYTHON": if s == "T": continue print(s,end="")

Python A 学习笔记 第二部分

for s in "PYTHON": if s == "T": break print(s,end="")

Python A 学习笔记 第二部分

由上面的测试可以看出,continue语句只结束本次循环,而不终止整个循环之星;而break语句则是结束整个循环过程,不再判断执行循环的条件是否成立。


4.4 random 库的使用

随机运算的标准函数库random一共提供9个常用函数。

这个库的主要作用是用于产生各种分布的伪随机数序列,采用的是梅森旋转算法来生成伪随机数序列,用于除随机性要求更高的加解密算法外的大多数工程应用。

4.4.1 random 库解析

上面提到:Python 的 random 库提供了 9 个随机数生成函数。如下表所示:

函数 描述
seed( a=None ) 初始化随机数种子,默认值为当前系统时间
random( ) 生成一个[0.0,1.0)之间的随机小数
randint ( a , b ) 生成一个[a,b]之间的整数
getrandbits( k ) 生成一个 k 比特长度的随机整数
randrange( start, stop[, step] ) 生成一个[ start,stop )之间以 step 为步数的随机整数
uniform( a,b ) 生成一个[ a,b ]之间的随机随机小数
choice( seq ) 从序列类型,例如列表中随机返回一个元素
shuffle( seq ) 将序列类型中的元素随机排序,返回打乱后的序列
sample( pop,k ) 从 pop 类型中随机选取 k 个元素,以列表类型返回

random库的引用方法与math库一样,可以采用下面两种方式实现:

import random
from random import *

需要注意的是,seed()函数指定随机数种子,随机数种子一般是一个整数,只要种子相同,每次生成的随机数序列也是相同的,这种情况便于测试和同步数据。而下面的这些语句每次执行后的结果不一定一样:

Python A 学习笔记 第二部分


4.5 程序的异常处理

4.5.1 异常处理 try-except语句

当我们在下面这段程序运行的时候,输入一个字符串:

num = eval(input("请输入一个整数:"))print(num**2)

Python A 学习笔记 第二部分

这个时候我们可以得到一个报错,报错的蓝色部分是出错的程序,line后面的数字代表着异常发生的代码行数。底下的NameError是异常类型,后面紧跟的是异常内容提示。

Python异常信息中最重要的部分是异常类型,它表明了发生异常的原因,也是程序处理异常的依据。这个时候我们可以用try-except语句抛出异常。

try: < 语句块1 >except < 异常类型 >: < 语句块2 >

我们根据这个语句结构为上述的程序添加异常处理:

try: num = eval(input("请输入一个整数:")) print(num**2)except NameError: print("输入错误,请输入一个整数!")

程序的运行结果如下:

Python A 学习笔记 第二部分

4.5.2 异常的高级用法

try-except语句支持多个except语句,类似if-elif-else的用法。其结构如下:

try: < 语句块1 >except < 异常类型1 >: < 语句块2 >...except < 异常类型N >: < 语句块 N+1 >except: < 语句块 N+2 >

除了try-except语句,还可以与elsefinally保留字配合使用:

try: < 语句块1 >except< 异常类型1 >: < 语句块2 >else: < 语句块3 >finally: < 语句块4 >

try中的语句块1正常执行结束且没有发生异常时,else中的语句块3执行,可以看作是对try语句块正常执行后的一种追加处理。finally语句块则不同,无论try中语句块1是否发生异常,语句块4都会执行,可以将程序执行语句块1的一些收尾工作放在这里。例如:关闭、打开文件等等。控制流过程如下所示:

Python A 学习笔记 第二部分


第五章 函数与代码复用


5.1 函数的基本使用

5.1.1 函数的定义

函数是一段具有特定功能的、可复用的语句组,用函数名来表示并通过函数名进行功能调用。可以在需要的地方调用而不用在每个执行的地方重复编写这些语句。

与黑盒类似,使用函数不需要了解函数内部的实现原理,只需要了解输入输出方式即可。

编写函数有两个目的:降低编程难度和代码复用。

Python使用def保留字定义一个函数,其语法形式如下:

def < 函数名 >(< 参数列表 >): < 函数体 > return < 返回值列表 >
  • 函数名可以是任何有效的Python标识符;

  • 参数列表是调用该函数时传递给它的值,可以没有也可以多个,当传递多个值的时候,各参数由逗号分隔,当没有参数的时候,圆括号也应当被保留。函数定义中的参数列表里面的参数是形式参数,简称为“形参”;

  • 函数体是函数每次被调用时执行的代码;

  • 当需要返回值时,使用保留字return和返回值列表。没有return语句时,在函数体结束位置,将控制权返回给调用者。

举个简单易懂的例子:

import osimport numpy as npimport cv2from PIL import Image

def start_recognize(model): model.summary() #输出模型各层的参数状况 class_names = ['1','2','3','4','5','6','7','8'] # 这个数组在模型训练的开始会输出 #要预测的图片保存在这里 predict_dir = './predict/' test = os.listdir(predict_dir) if '.DS_Store' in test: test.remove('.DS_Store') #新建一个列表保存预测图片的地址 images = [] #获取每张图片的地址,并保存在列表images中 for testpath in test: #循环获取测试路径底下需要测试的图片 for fn in os.listdir(os.path.join(predict_dir, testpath)): if fn.endswith('JPG'): fd = os.path.join(predict_dir, testpath, fn) #print(fd) images.append(fd) elif fn.endswith('jpg'): fd = os.path.join(predict_dir, testpath, fn) #print(fd) images.append(fd) elif fn.endswith('png'): fd = os.path.join(predict_dir, testpath, fn) #print(fd) images.append(fd) result_list = [] for img in images: imgpath = img #print(img) img_init = cv2.imread(img) img_init = cv2.resize(img_init, (224, 224)) # 将图片大小调整到224*224用于模型推理 cv2.imwrite(img, img_init) img = Image.open(img) # 读取图片 img = np.asarray(img) # 将图片转化为numpy的数组 outputs = model.predict(img.reshape(1, 224, 224, 3)) # 将图片输入模型得到结果 result_index = int(np.argmax(outputs)) result = class_names[result_index] #result.setText(result) #return img,result #返回图片地址和识别出的数字
imgf = imgpath.split('/')[3] imgb = imgf.split('.')[0] #print(result) result_list.append([imgb,result])
result_list = sorted(result_list, key=(lambda x:x[0]))
return result_list #返回二维列表,第一项是照片顺序大小,第二项是识别出后的数字

start_recognize是一个我封装的,用于进行图像识别的一个函数,其他人要调用该函数进行图像识别,但是那个人并不需要了解到这个函数是如何工作的,只需要了解到输入一个预加载的模型,就能得到识别后的数字和顺序。def start_recognize(model)中的model是一个形参,来指代输入函数的实际变量。

model = tf.keras.models.load_model("models/number_rcog_mobilenet.h5")

这个形参的值如上所示,是一个预加载的模型。考虑到这个这个函数会被经常调用,因此模型在程序开始运行之初,就被预加载到系统当中,每次调用函数的时候就不需要重新加载一遍模型了。

 

5.1.2 函数的调用过程

程序调用一个函数需要执行以下4个步骤:

  1. 调用程序在调用处暂停执行;

  2. 在调用时将实参复制给函数的形参;

  3. 执行函数体语句;

  4. 函数调用结束给出返回值,程序回到调用前的暂停处继续执行。

拓展:函数式编程

函数式编程是一种编程范式,常见的编程范式还包括命令式编程和面向对象编程等。函数式编程的主要思想是把程序过程尽量写成一系列函数调用,通过函数进一步提高封装级别。函数式编程通过使用一系列函数能够使代码编写更简介、更易于理解。是中小规模软件项目中最常用的编程方式。

 

5.1.3 lambda 函数

lambda函数用于定义一种特殊的函数--匿名函数,又称为lambda函数。匿名函数并非没有名字,而是将函数名作为函数结果返回,其语法格式如下:

< 函数名 > = lambda < 参数列表 >:< 表达式 >等价于下面的形式:def < 函数名 >(< 参数列表 >): return < 函数体 >

简单的来讲,lambda函数用于定义简单的、能够在一行内表示的函数,返回一个函数类型。


5.2 函数的参数传递

5.2.1 可选参数和可变数量参数

可选参数类型可以理解为:一个函数的参数存在一定的默认值,不一定需要调用的程序输入。当函数被调用的时候,若没有传入对应的参数,则使用函数定义时的默认值替代:

Python A 学习笔记 第二部分

可变数量参数需要在定义参数时在参数前面加上星号实现。可变参数只能出现在参数列表的最后,在被调用时,这些参数将被当做元组类型传递到函数中。

Python A 学习笔记 第二部分

5.2.2 参数的位置和名称传递

当函数的参数较多的时候,就会出现可读性较差的情况。如果不看函数的定义,在阅读程序的时候,很难理解这些参数含义。因此,Python提供了按照形参名输入实参的方式。

#一个多参数列表的函数:def XYZ(x1,y1,z1,x2,y2,z2): return#使用按照形参名输入实参的方法:result = XYZ(x1=1,x2=2,y1=3,y2=4,z1=6,z2=5)

可以看到:参数的传入顺序并没有按照参数列表的顺序。这是因为:在调用函数的时候,指定了参数名称,所以参数之间的顺序可以任意调整。

5.2.3 函数的返回值

return语句可以将0个或多个函数结果返回给函数被调用处的变量,返回多个值的时候以元组类型保存,但是函数也可以没有return,此时的函数没有返回值。

5.2.4 函数对变量的作用

  • 全局变量:指在函数之外定义的变量,一般没有缩进,在程序执行的全过程有效

  • 局部变量:指在函数内部使用的变量,仅在函数内部有效,当函数退出时变量将不存在

n=1 #n是全局变量def func(a,b): c=a*b #c是局部变量,a和b作为函数参数也是局部变量 return cs=func("knock~",2)print(c)

Python A 学习笔记 第二部分

上面的例子说明:当函数执行完退出后,其内部变量将被释放。

如果再函数内部使用了全局变量呢?

n=1 #n是全局变量def func(a,b): n=b #这个n是在函数内存中新生成的局部变量,不是全局变量 return a*bs=func("knock~",2)print(s,n) #测试一下n值是否发生改变

Python A 学习笔记 第二部分

我们看到:全局变量 n 并没有发生变化,这是因为函数func()内部有自己的空间,这时函数内的 n 并不是全局变量而是局部变量,如果要在函数内使用全局变量 n 的话,需要在使用前声明该变量为全局变量。

n=1 #n是全局变量def func(a,b): global n n=b #将局部变量b赋值给全局变量n return a*bs=func("knock~",2)print(s,n) #测试一下n值是否发生改变

Python A 学习笔记 第二部分

这个时候可以看到:全局变量 n 发生了变化。

简单总结一下,Python函数对变量的作用遵守以下原则:

  1. 简单数据类型变量无论是否与全局变量重名,仅在函数内部创建和使用,函数退出后变量被释放,如有全局同名变量,其值不变。

  2. 简单数据类型变量在用global保留字声明后,作为全局变量使用,函数退出后该变量保留且值被函数改变。

  3. 对于组合数据类型的全局变量,如果在函数内部没有被真实创建的同名变量,则函数内部可以直接使用并修改全局变量的值。

  4. 如果函数内部真实创建了组合数据类型变量,无论是否有同名全局变量,函数仅对局部变量进行操作,函数退出后局部变量被释放,全局变量值不变。


5.3 代码复用和模块化设计

为了提高程序的可读性和可维护性、可升级性,因此需要对代码进行抽象,形成易于理解的结构。

Python A 学习笔记 第二部分


函数是一种基本抽象方式,将一系列代码组织起来,通过命名供其他程序使用。其有两个优点:

  1. 方便代码复用,其他任何代码只需要输入参数即可调用函数,从而避免相同功能的代码重复编写;

  2. 更新函数功能时,所有被调用处的功能都会被更新。

面向过程是一种基本且自然的程序设计方法,然后将一步一步实现的步骤、子功能封装、实现代码复用,简化程序设计难度。


对象是程序的一种高级抽象方式,将程序代码组织成为更高级别的类。对象包括表征对象特征的属性和代表对象操作的方法。例如:汽车是一个对象,其颜色、车型、轮胎的数量则是属性,代表汽车的静态值;而前进、后退、转弯等是方法,代表汽车的动作和行为。

简单地讲,对象是程序拟解决计算问题的一个高级别抽象,它包括一组静态值(属性)和一组函数(方法)。

对象和函数都使用了一个容易理解的抽象逻辑,但对象可以凝聚更多代码。因此对象编程适合更大规模代码、交互逻辑复杂的程序。

当程序的长度在上百行的时候,可读性就会变得非常的糟糕。

这让我想起来我在高中时候参与的一个项目中的一部分:数据清洗和筛选,上百行纯面向过程的,没有封装函数的Python程序,修改程序的时候简直心脏骤停。好在当时使用的jupyter能分段执行...

因此对于大规模程序而言,应当对程序合理划分功能模块,并基于模块设计程序。这也是一种常用的方式,叫做“模块化设计”。模块化设计指通过函数或对象的封装功能将程序划分成主程序、子程序和子程序间关系的表达。模块化设计是使用函数和对象设计程序的思考方法,以功能块为基本单位,一般有以下两个基本要求:

  1. 紧耦合:尽可能合理划分功能块,功能块内部耦合紧密;

  2. 松耦合:模块间关系尽可能简单,功能块之间耦合度低;

使用函数只是模块化设计的必要非充分条件。一般来说,完成特定功能或被经常复用的一组语句应该采用函数来封装,并尽量减少函数间参数和返回值的数量。


5.4 函数的递归

5.4.1 递归的定义

一个函数被函数内部代码调用,这种函数定义中调用函数自身的方式叫做“递归”。在数学上经典的例子就是阶乘。

阶乘的例子解释了递归的两个关键特征:

  1. 存在一个或多个基例,所谓基例就是不需要再次递归,是一个确定的表达式;

  2. 所有递归链要以一个或多个基例结尾;

5.4.2 递归的使用方法

这里只放一个 5!五的阶乘的例子。