1. Python简介

1.1 Python 定义

是一个免费、开源、跨平台、动态、面向对象的编程语言。

1.2 Python程序的执行方式

(1)交互式

在命令行输入指令,回车即可得到结果。

(1) 打开终端

(2) 进入交互式:python3

(3) 编写代码:print(“hello world”)

(4) 离开交互式:exit()

(2)文件式

将指令编写到.py文件,可以重复运行程序。

(1) 编写文件

(2) 打开终端

(3) 进入程序所在目录:cd 目录

(4) 执行程序: python3 文件名

1.3 执行过程

计算机只能识别机器码(1010),不能识别源代码(python)。由源代码转变成机器码的过程分成两类:编译和解释。

(1)编译

在程序运行之前,通过编译器将源代码变成机器码,例如:C语言。

  • 优点:运行速度快
  • 缺点:开发效率低,不能跨平台。

(2)解释

在程序运行之时,通过解释器对程序逐行翻译,然后执行,例如Javascript。

  • 优点:开发效率高,可以跨平台;
  • 缺点:运行速度慢。

(3)Python是解释型语言

Python是解释型语言,但为了提高运行速度,使用了一种编译的方法。编译之后得到pyc文件,存储了字节码(特定于Python的表现形式,不是机器码)。

源代码 – 编译 –> 字节码 – 解释 –> 机器码

1.4 解释器类型

(1) CPython(C语言开发)

(2) Jython (java开发)

(3) IronPython (.net开发)

2. 数据基本运算

2.1 基础知识

(1) pycharm常用快捷键

  • 移动到本行开头:home键

  • 移动到本行末尾:end键盘

  • 注释代码:ctrl + /

  • 复制行:ctrl +d
  • 删除行:shift + delete
  • 选择列:shift + alt +鼠标左键
  • 移动行:ctrl + shift + 上下箭头
  • 代码格式化: ctrl+alt+l
  • 查看函数参数:ctrl+P
  • 重构成员:shift+F6

(2)注释

​ 给人看的,通常是对代码的描述信息。

  • 单行注释:以#号开头。
  • 多行注释:三引号开头,三引号结尾。

2.2 数据

  • 整形int

  • 浮点型float

  • 字符串str

  • 布尔bool

2.3 运算

(1) 类型转换

  • 转换为整形: int(数据)
  • 转换为浮点型:float(数据)
  • 转换为字符串:str(数据)
  • 转换为布尔:bool(数据)

(2)运算符

(1)算术运算符
  • + 加法
  • - 减法
  • * 乘法
  • / 除法:结果为浮点数
  • // 整除:除的结果去掉小数部分
  • % 求余
  • ** 幂运算
(2) 增强运算符
  • y += x 相当于 y = y + x
  • y -= x 相当于 y = y - x
  • y *= x 相当于 y = y * x
  • y /= x 相当于 y = y / x
  • y //= x 相当于 y = y // x
  • y %= x 相当于 y = y % x
  • y **= x 相当于 y = y ** x
(3) 比较运算符
  • < 小于
  • <= 小于等于
  • > 大于

  • >= 大于等于

  • == 等于
  • != 不等于
(4) 逻辑运算符
  • 与and
  • 或or
  • 非 not
(5)身份运算符
  • 语法:

    x is y

    x is not y

  • 作用:

    is 用于判断两个对象是否是同一个对象,是时返回True,否则返回False。

(6)优先级

​ 高到低:

​ 算数运算符

​ 比较运算符

​ 增强运算符

​ 身份运算符

​ 逻辑运算符

3. 语句

3.1 条件语句

(1)if elif 语句

  • 作用:

让程序根据条件选择性的执行语句。

  • 语法:
1
2
3
4
5
6
if 条件1:
语句块1
elif 条件2:
语句块2
else:
语句块3

(2)真值表达式

1
2
3
4
5
if 100:
print("真值")
# 等同于
if bool(100):
print("真值")

(3)条件表达式

  • 语法:变量 = 结果1 if 条件 else 结果2
1
value = 1 if input("请输入性别:") == "男" else 0
  • 作用:根据条件(True/False) 来决定返回结果1还是结果2。

3.2 循环语句

(1)while语句

(2)for 语句

  • 作用:用来遍历可迭代对象的数据元素。可迭代对象是指能依次获取数据元素的对象,例如:容器类型。
  • 语法:
1
2
for 变量列表 in 可迭代对象:
# 语句块

(3)range 函数

  • 作用:

​ 用来创建一个生成一系列整数的可迭代对象(也叫整数序列生成器)。

  • 语法:

range(开始点,结束点,间隔)

  • 说明:

    函数返回的可迭代对象可以用for取出其中的元素

    返回的数字不包含结束点

    开始点默认为0

    间隔默认值为1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 写法1:range(开始,结束,间隔)
# 注意:不包含结束值
for item in range(1, 3, 1):
print(item)

# 写法2:range(开始,结束)
# 注意:间隔默认为1
for item in range(1, 3):
print(item)

# 写法3:range(结束)
# 注意:开始默认为0
for item in range(3):
print(item)

3.3 跳转语句

(1)break 语句

​ 跳出循环体,后面的代码不再执行。

(2)continue 语句

​ 跳过本次,继续下次循环。

4. Python基础容器

Python基础容器

5. 函数 function

5.1 定义

  • 用于封装一个特定的功能,表示一个功能或者行为。
  • 函数是可以重复执行的语句块, 可以重复调用。
  • 提高代码的可重用性和可维护性(代码层次结构更清晰)。

5.2 基础语法

(1)定义函数

  • 语法:

    def 函数名(形式参数):

       函数体
    
  • 说明:

def 关键字:全称是define,意为”定义”。

函数名:对函数体中语句的描述,规则与变量名相同。

形式参数:函数定义者要求调用者提供的信息。

函数体:完成该功能的语句。

  • 函数的第一行语句建议使用文档字符串描述函数的功能与参数。

(2)调用函数

  • 语法:函数名(实际参数)
  • 说明:根据形参传递内容。

(3)返回值

  • 定义:

    函数定义者告诉调用者的结果。

  • 语法:

    return 数据

  • 说明:

    return后没有语句,相当于返回 None。

    函数体没有return,相当于返回None。

5.3 可变/不可变类型

(1) 不可变类型参数:

  • 数值型(整数,浮点数)
  • 布尔值bool
  • None 空值
  • 字符串str
  • 元组tuple

(2) 可变类型参数:

  • 列表 list
  • 字典 dict
  • 集合 set

(3) 传参说明:

  • 不可变类型的数据传参时,函数内部不会改变原数据的值。
  • 可变类型的数据传参时,函数内部可以改变原数据。

5.4 函数参数

(1)实参传递方式

位置传参

​ 定义:实参与形参的位置依次对应。

1
2
3
4
5
6
7
def fun01(p1, p2, p3):
print(p1)
print(p2)
print(p3)

# 位置实参:根据顺序与形参进行对应
fun01(1, 2, 3)
序列传参

​ 定义:实参用*将序列拆解后与形参的位置依次对应。

1
2
3
4
5
6
7
8
9
10
11
def func01(p1, p2, p3):
print(p1, p2, p3)

# 序列实参:拆,按照顺序与形参对应
list01 = [1, 2, 3]
name = "孙悟空"
tuple01 = (4, 5, 6)
# func01(list01)
func01(*list01) # 拆 1, 2, 3
func01(*name) # 拆 孙 悟 空
func01(*tuple01) # 拆 4, 5, 6
关键字传参

​ 定义:实参根据形参的名字进行对应。

1
2
3
4
5
6
7
8
def fun01(p1, p2, p3):
print(p1)
print(p2)
print(p3)

# 关键字实参:根据名字与形参进行对应
fun01(p1=1, p2=2, p3=3)
fun01(p2=2, p1=1, p3=3)
字典关键字传参

​ 定义:实参用**将字典拆解后与形参的名字进行对应。

​ 作用:配合形参的缺省参数,可以使调用者随意传参。

1
2
3
4
5
6
def func01(p1, p2, p3):
print(p1, p2, p3)

# 字典实参:拆,按照名称与形参对应
dict01 = {"p2":"B","p1":"A","p3":"C"}
func01(**dict01)

(2)形参定义方式

缺省形参
  • 语法:

    def 函数名(形参名1=默认实参1, 形参名2=默认实参2, …):

    ​ 函数体

  • 说明:

    缺省参数必须自右至左依次存在,如果一个参数有缺省参数,则其右侧的所有参数都必须有缺省参数。

1
2
3
4
5
6
7
8
9
10
11
def func01(p1=0, p2="", p3=100):
print(p1)
print(p2)
print(p3)

func01(p2=2)
func01(p2=2,p3=3)
# 支持同时使用位置实参与关键字实参
func01(1,p3=3)
# 注意1:先位置实参,后关键字实参
# func01(p1=1,2,3) # 错误
位置形参
  • 语法:

    ​ def 函数名(形参名1, 形参名2, …):

    ​ 函数体

命名关键字形参
  • 语法:

    def 函数名(*args, 命名关键字形参1, 命名关键字形参2, …):

       函数体
    

    def 函数名(*, 命名关键字形参1, 命名关键字形参2, …):

    ​ 函数体

  • 作用:

    强制实参使用关键字传参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 命名关键字形参:
# 星号元组形参后面的位置形参
# 限制实参必须是关键字实参
def func01(*args, p1, p2):
print(args)
print(p1)
print(p2)


func01(p1=1, p2=2)
func01(1, 2, 3, p1=1, p2=2)


def func02(p1, *, p2=0):
print(p1)
print(p2)


# 通常星号后面的命名关键字形参属于辅助参数,可选.
func02(1)
func02(1, p2=2)
星号元组形参
  • 语法:

    def 函数名(*元组形参名):

    ​ 函数体

  • 作用:

    可以将多个位置实参合并为一个元组

  • 说明:

    一般命名为’args,形参列表中最多只能有一个

1
2
3
4
5
6
7
8
# 位置实参数量可以无限
def func01(*args):
print(args)

func01() # 空元组
func01(1, 2, 34) # (1, 2, 34)
# 不支持关键字实参
# func01(args=1,a=1)
双星号字典形参
  • 语法:

    def 函数名(**字典形参名):

    ​ 函数体

  • 作用:

    可以将多个关键字实参合并为一个字典

  • 说明:

    一般命名为‘kwargs

    形参列表中最多只能有一个

1
2
3
4
5
6
# 关键字实参数量无限
def func01(**kwargs):
print(kwargs) # {'a': 1, 'b': 2}

func01(a=1,b=2)
# func01(1,2,3) # 报错
⑥参数自左至右的顺序

位置形参 –> 星号元组形参 –> 命名关键字形参 –> 双星号字典形参

练习:说出程序执行结果.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def func01(list_target):
print(list_target)# ?

def func02(*args):
print(args)# ?

def func03(*args,**kwargs):
print(args)# ?
print(kwargs)# ?

def func04(p1,p2,*,p4,**kwargs):
print(p1)# ?
print(p2)# ?
print(p4)# ?
print(kwargs)# ?

func01([1,2,3])
func02(*[1,2,3])
func03(1,2,3,a=4,b=5,c=6)
func04(10,20,p4=30,p5=40)

6. 作用域

6.1 定义

变量起作用的范围。

6.2 分类

(1) Local 局部作用域:函数内部。

(2) Enclosing 外部嵌套作用域 :函数嵌套。

(3) Global 全局作用域:模块(.py文件)内部。

(4) Builtin 内置模块作用域:builtins.py文件。

6.3 变量名的查找规则

(1) 由内到外:L -> E -> G -> B

(2) 在访问变量时,先查找本地变量,然后是包裹此函数外部的函数内部的变量,之后是全局变量,最后是内置变量。

6.4 局部变量

(1) 定义在函数内部的变量(形参也是局部变量)

(2) 只能在函数内部使用

(3) 调用函数时才被创建,函数结束后自动销毁

6.5 全局变量

(1) 定义在函数外部,模块内部的变量。

(2) 在整个模块(py文件)范围内访问(但函数内不能将其直接赋值)。

6.6 global 语句

(1) 作用:

​ 在函数内部修改或定义全局变量(全局声明)。

(2) 语法:

​ global 变量1, 变量2, …

(3) 说明

​ 在函数内直接为全局变量赋值,视为创建新的局部变量。

​ 不能先声明局部的变量,再用global声明为全局变量。

6.7 nonlocal 语句

(1) 作用:

​ 在内层函数修改外层嵌套函数内的变量

(2) 语法

​ nonlocal 变量名1,变量名2, …

(3) 说明

​ 在被嵌套的内函数中进行使用

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
27
28
29
30
31
32
33
34
35
36
37
# 2. 全局作用域:文件内部
# 全局变量:在全部作用域内创建的变量
# 适用范围:整个文件
data02 = 20
data03 = [30]

def func01():
# 1. 局部作用域:函数内部
# 局部变量:在局部作用域内创建的变量
# 适用范围:一个函数
data01 = 10
print(data01)
print(data02)

def func02():
# print(data01) # 不能访问其他函数局部变量
print(data02) # 读取全局变量

def func03():
# 在局部作用域中不能修改全局变量
# data02 = 200
# 必须通过global语句声明
global data02
data02 = 200

def func04():
# 没有修改全局变量
# 在修改全局变量指向的列表
# 所以不需要通过global语句声明
data03[0] = 300

func01()
func02()
func03()
func04()
print(data02) # 200
print(data03) # [300]

练习:画出下列代码内存图

1
2
3
4
5
6
7
8
9
10
11
12
data01 = 10

def func01(p):
global data01
data01 += 1
p += 1

data02 = 10
func01(data02)
func01(data02)
print(data01) # ?
print(data02) # ?

7. 面向对象简介

7.1 面向过程

(1) 定义:分析出解决问题的步骤,然后逐步实现。

​ 例如:婚礼筹办

​ – 请柬(选照片、措词、制作)

​ – 宴席(场地、找厨师、准备桌椅餐具、计划菜品、购买食材)

​ – 仪式(定婚礼仪式流程、请主持人)

(2) 公式程序 = 算法 + 数据结构

(3) 优点:所有环节、细节自己掌控。

(4) 缺点:考虑所有细节,工作量大。

image-20210311120846382

7.2 面向对象

(1) 定义:找出解决问题的人,然后分配职责。

​ 例如:婚礼筹办

​ – 发请柬:找摄影公司(拍照片、制作请柬)

​ – 宴席:找酒店(告诉对方标准、数量、挑选菜品)

​ – 婚礼仪式:找婚庆公司(对方提供司仪、制定流程、提供设备、帮助执行)

(2) 公式程序 = 对象 + 交互

(3) 优点

​ a. 思想层面:

​ – 可模拟现实情景,更接近于人类思维。

​ – 有利于梳理归纳、分析解决问题。

​ b. 技术层面:

​ – 高复用:对重复的代码进行封装,提高开发效率。

​ – 高扩展:增加新的功能,不修改以前的代码。

​ – 高维护:代码可读性好,逻辑清晰,结构规整。

(4) 缺点:学习曲线陡峭。

8. 类和对象

8.1 语法

(1)定义类

  • 代码
1
2
3
4
5
6
7
8
class 类名:
"""
文档说明
"""
def __init__(self,参数):
self.实例变量 = 参数

方法成员
  • 说明
    • 类名所有单词首字母大写.
    • init 也叫构造函数,创建对象时被调用,也可以省略。
    • self 变量 绑定的是被创建的对象,名称可以随意。

(2)实例化对象

  • 代码
1
变量 = 类名(参数)
  • 说明
  • 变量存储的是实例化后的对象地址
  • 类名后面的参数按照构造函数的形参传递

2.2 实例成员

(1) 实例变量

  • 定义

    1
    对象.变量名
  • 调用

    1
    对象.变量名 
  • 说明

  • 首次通过对象赋值为创建,再次赋值为修改.

  • 通常在构造函数(__init_)中创建

  • 每个对象存储一份,通过对象地址访问

  • 作用:描述某个对象的数据。
  • _dict_:对象的属性,用于存储自身实例变量的字典。

(2)实例方法

  • 定义
1
2
def 方法名称(self, 参数):
方法体
  • 调用:
1
2
对象.方法名称(参数)
# 不建议通过类名访问实例方法
  • 说明

    • 至少有一个形参,第一个参数绑定调用这个方法的对象,一般命名为self。
    • 无论创建多少对象,方法只有一份,并且被所有对象共享。
  • 作用:表示对象行为。

2.3 类成员

(1)类变量

  • 定义:在类中,方法外。
1
2
class 类名:
变量名 = 数据
  • 调用:
1
2
类名.变量名
# 不建议通过对象访问类变量
  • 特点:

    • 随类的加载而加载

    • 存在优先于对象

    • 只有一份,被所有对象共享。

  • 作用:描述所有对象的共有数据。

(2) 类方法

  • 定义:
1
2
3
@classmethod
def 方法名称(cls,参数):
方法体
  • 调用:
1
2
类名.方法名(参数) 
# 不建议通过对象访问类方法
  • 说明

    • 至少有一个形参,第一个形参用于绑定类,一般命名为’cls’
    • 使用@classmethod修饰的目的是调用类方法时可以隐式传递类。
  • 类方法中不能访问实例成员,实例方法中可以访问类成员。

  • 作用:操作类变量。

2.4 静态方法

  • 定义:
1
2
3
@staticmethod
def 方法名称(参数):
方法体
  • 调用:
1
2
类名.方法名称(参数) 
# 不建议通过对象访问静态方法
  • 说明
    • 使用@ staticmethod修饰的目的是该方法不需要隐式传参数。
    • 静态方法不能访问实例成员和类成员
  • 作用:定义常用的工具函数。

9. 面向对象三大特征

9.1 封装

(1)数据角度

  • 定义:将一些基本数据类型复合成一个自定义类型。
  • 优势:

    • 将数据(变量)与对数据的操作(方法)相关联。

    • 代码可读性更高(类是对象的模板)。

(2)行为角度

  • 定义:

    向类外提供必要的功能,隐藏实现的细节。

  • 优势:

    简化编程,使用者不必了解具体的实现细节,只需要调用对外提供的功能。

(3)设计角度

  • 分而治之
    • 将一个大的需求分解为许多类,每个类处理一个独立的功能
    • 拆分好处:便于分工,便于复用,可扩展性强
  • 变则疏之
    • 变化的地方独立封装,避免影响其他类
  • 高内聚
    • 类中各个方法都在完成一项任务(单一职责的类)
  • 低耦合
    • 类与类的关联性与依赖度较低(每个类独立),让一个类改变尽量不影响其他类。

(4)私有成员

  • 作用:无需向类外提供的成员,可以通过私有化进行屏蔽。
  • 做法:命名使用双下划线开头
  • 本质:障眼法,实际也可以访问。

​ 私有成员的名称被修改为:_类名__成员名,可以通过___dict__属性查看。

– 演示

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyClass:
def __init__(self, data):
self.__data = data

def __func01(self):
print("func01执行了")

m01 = MyClass(10)
# print(m01.__data) # 无法访问
print(m01._MyClass__data)
print(m01.__dict__) # {'_MyClass__data': 10}
# m01.__func01() # 无法访问
m01._MyClass__func01()

(5)属性@property

  • 作用:保护实例变量(读取、只读、只写)
  • 定义:
1
2
3
4
5
6
7
@property
def 属性名(self):
return self.__属性名

@属性名.setter
def 属性名(self, value):
self.__属性名= value
  • 调用:
1
2
对象.属性名 = 数据
变量 = 对象.属性名
  • 三种形式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1. 读写属性
class MyClass:
def __init__(self,data):
self.data = data

@property
def data(self):
return self.__data

@data.setter
def data(self, value):
self.__data = value

m01 = MyClass(10)
print(m01.data)
1
2
3
4
5
6
7
8
9
10
11
12
13
# 2. 只读属性
class MyClass:
def __init__(self):
self.__data = 10

@property
def data(self):
return self.__data


m01 = MyClass()
# m01.data = 20# AttributeError: can't set attribute
print(m01.data)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 3. 只写属性
class MyClass:
def __init__(self, data):
self.data = data

# data = property()

# @data.setter
# def data(self, value):
# self.__data = value

def data(self, value):
self.__data = value

data = property(fset=data)


m01 = MyClass(10)
print(m01.data) # AttributeError: unreadable attribute
m01.data = 20

9.2 继承

(1)继承方法

  • 语法:
1
2
3
4
5
6
7
8
9
10
11
class 父类:
def 父类方法(self):
方法体

class 子类(父类):
def 子类方法(self):
方法体

儿子 = 子类()
儿子.子类方法()
儿子.父类方法()
  • 说明:

    子类直接拥有父类的方法.

  • 演示:

(2)内置函数

  • isinstance(对象, 类型)

    ​ 返回指定对象是否是某个类的对象。

  • issubclass(类型,类型)

    ​ 返回指定类型是否属于某个类型。

(3)继承数据

  • 语法
1
2
3
4
class 子类(父类):
def __init__(self,父类参数,子类参数):
super().__init__(参数) # 调用父类构造函数
self.实例变量 = 参数
  • 说明

子类如果没有构造函数,将自动执行父类的,但如果有构造函数将覆盖父类的。此时必须通过super()函数调用父类的构造函数,以确保父类实例变量被正常创建。

  • 演示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Person:
def __init__(self, name="", age=0):
self.name = name
self.age = age

# 子类有构造函数,不会使用继承而来的父类构造函数[子覆盖了父方法,好像它不存在]
class Student(Person):
# 子类构造函数:父类构造函数参数,子类构造函数参数
def __init__(self, name, age, score):
# 调用父类构造函数
super().__init__(name, age)

self.score = score

ts = Person("唐僧",22)
print(ts.name)
kw = Student("悟空", 23, 100)
print(wk.name)
print(wk.score)

(4) 练习:

创建父类:车(品牌,速度)

创建子类:电动车(电池容量,充电功率)

创建子类对象并画出内存图。

(4) 定义

  • 概念: 重用现有类的功能,并在此基础上进行扩展。
  • 说明:子类直接具有父类的成员(共性),还可以扩展新功能。
  • 相关知识

    • 父类(基类、超类)、子类(派生类)。

    • 父类相对于子类更抽象,范围更宽泛;子类相对于父类更具体,范围更狭小。

    • 单继承:父类只有一个(例如 Java,C#)。

    • 多继承:父类有多个(例如C++,Python)。

    • Object类:任何类都直接或间接继承自 Object 类。

(5)多继承

  • 定义:一个子类继承两个或两个以上的基类,父类中的属性和方法同时被子类继承下来。
  • 同名方法解析顺序(MRO, Method Resolution Order):

    类自身 –> 父类继承列表(由左至右)–> 再上层父类

​ A

​ / \

/ \

B C

\ /

​ \ /

​ D

(3) 练习:写出下列代码在终端中执行效果

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
class A:
def func01(self):
print("A")
super().func01()

class B:
def func01(self):
print("B")

class C(A,B):
def func01(self):
print("C")
super().func01()

class D(A, B):
def func01(self):
print("D")
super().func01()

class E(C,D):
def func01(self):
print("E")
super().func01()

e = E()
e.func01()

9.3 多态

(1)重写内置函数

  • 定义:Python中,以双下划线开头、双下划线结尾的是系统定义的成员。我们可以在自定义类中进行重写,从而改变其行为。
  • __str__ 函数:将对象转换为字符串(对人友好的)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person:
def __init__(self, name="", age=0):
self.name = name
self.age = age

def __str__(self):
return f"{self.name}的年龄是{self.age}"

wk = Person("悟空", 26)
# <__main__.Person object at 0x7fbabfbc3e48>
# 悟空的年龄是26
print(wk)
# message = wk.__str__()
# print(message)

  • 算数运算符重载

image-20210305133828932

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Vector2:
"""
二维向量
"""

def __init__(self, x, y):
self.x = x
self.y = y

def __str__(self):
return "x是:%d,y是:%d" % (self.x, self.y)

def __add__(self, other):
return Vector2(self.x + other.x, self.y + other.y)

v01 = Vector2(1, 2)
v02 = Vector2(2, 3)
print(v01 + v02) # v01.__add__(v02)
  • 复合运算符重载

image-20210305133752412

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
27
28
class Vector2:
"""
二维向量
"""

def __init__(self, x, y):
self.x = x
self.y = y

def __str__(self):
return "x是:%d,y是:%d" % (self.x, self.y)

# + 创建新
def __add__(self, other):
return Vector2(self.x + other.x, self.y + other.y)

# += 在原有基础上修改(自定义类属于可变对象)
def __iadd__(self, other):
self.x += other.x
self.y += other.y
return self

v01 = Vector2(1, 2)
v02 = Vector2(2, 3)
print(id(v01))
v01 += v02
print(id(v01))
print(v01)
  • 比较运算重载

image-20210305133720536

– 演示

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
27
28
29
30
31
32
33
34
35
36
37
38
39
class Vector2:
"""
二维向量
"""

def __init__(self, x, y):
self.x = x
self.y = y

# 决定相同的依据
def __eq__(self, other):
return self.x == other.x and self.y == other.y

# 决定大小的依据
def __lt__(self, other):
return self.x < other.x


v01 = Vector2(1, 1)
v02 = Vector2(1, 1)
print(v01 == v02) # True 比较两个对象内容(__eq__决定)
print(v01 is v02) # False 比较两个对象地址

list01 = [
Vector2(2, 2),
Vector2(5, 5),
Vector2(3, 3),
Vector2(1, 1),
Vector2(1, 1),
Vector2(4, 4),
]

# 必须重写 eq
print(Vector2(5, 5) in list01)
print(list01.count(Vector2(1, 1)))

# 必须重写 lt
list01.sort()
print(list01)

(2)重写自定义函数

  • 子类实现了父类中相同的方法(方法名、参数),在调用该方法时,实际执行的是子类的方法。
  • 快捷键:ctrl + O
  • 作用
  • 在继承的基础上,体现类型的个性(一个行为有不同的实现)。
  • 增强程序灵活性。

10. 程序结构

10.1 模块 Module

(1)定义

​ 包含一系列数据、函数、类的文件,通常以**.py**结尾。

(2)作用

​ 让一些相关的数据,函数,类有逻辑的组织在一起,使逻辑结构更加清晰。有利于多人合作开发。

(3)导入

  • import

    • 语法:

      import 模块名

      import 模块名 as 别名

    • 作用:将模块整体导入到当前模块中

    • 使用:

      模块名.成员

      别名.成员

  • from import

    • 语法:

      from 模块名 import 成员名

      from 模块名 import 成员名 as 别名

      from 模块名 import *

    • 作用:将模块内的成员导入到当前模块作用域中

    • 使用:直接使用成员名

(4)模块变量

  • __doc__变量:文档字符串。
  • __name__变量:模块自身名字,可以判断是否为主模块。

    当此模块作为主模块(第一个运行的模块)运行时,__name__绑定’__main__‘,不是主模块,而是被其它模块导入时,存储模块名。

(5) 加载过程

  • 在模块导入时,模块的所有语句会执行。
  • 如果一个模块已经导入,则再次导入时不会重新执行模块内的语句。

(6)分类

  • 内置模块(builtins),在解析器的内部可以直接使用。
  • 标准库模块,安装Python时已安装且可直接使用。
  • 第三方模块(通常为开源),需要自己安装。
  • 用户自己编写的模块(可以作为其他人的第三方模块)

10.2 包package

(1)定义

​ 将模块以文件夹的形式进行分组管理。让一些相关的模块组织在一起,使逻辑结构更加清晰。

(2)__init.py的作用

  • _init_.py 在包被导入时会被执行

  • 表示(标识)一个Python Package

  • 对于自己写的模块,想要一键导入,单单这样写:

1
from package import *

​ 是不够的,要在_init_.py中加入

1
__all__ = [‘module1’,‘module2’,.]

(3)导入

  • import

    • 语法:

      import 包

      import 包 as 别名

    • 作用:将包中__init__模块内整体导入到当前模块中

    • 使用:包.成员

  • from import

    • 语法:

      from 包 import 成员

      from 包 import 成员 as 别名

    • 作用:将包中__init__模块内的成员导入到当前模块作用域中

    • 使用:直接使用成员名

  • 演示:

    目录结构:

    ​ main.py

    ​ package01/

    ​ __init__.py

    ​ module01.py

1
2
3
4
5
6
7
8
9
"""
package01/
module01.py
"""
def func01():
print("func01执行了")

def func02():
print("func02执行了")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
"""
main.py
"""
# 方式1:import 包 as 别名
import package01 as p

p.module01.func01()
p.func02()

# 方式2:from 包 import 成员
from package01 import module01,func02

module01.func01()
func02()
1
2
3
4
5
6
7
"""
package01/
__init__.py
"""
import package01.module01

from package01.module01 import func02

11. 异常处理

11.1 异常

(1)定义

​ 运行时检测到的错误。

(2)现象

​ 当异常发生时,程序不会再向下执行,而转到函数的调用语句。

(3)常见异常类型

  • 名称异常(NameError):变量未定义。
  • 类型异常(TypeError):不同类型数据进行运算。

  • 索引异常(IndexError):超出索引范围。

  • 属性异常(AttributeError):对象没有对应名称的属性。

  • 键异常(KeyError):没有对应名称的键。

  • 异常基类Exception

11.2 处理

(1)语法:

1
2
3
4
5
6
7
8
9
10
11
12
try:
可能触发异常的语句
except 错误类型1 [as 变量1]:
处理语句1
except 错误类型2 [as 变量2]:
处理语句2
except Exception [as 变量3]:
不是以上错误类型的处理语句
else:
未发生异常的语句
finally:
无论是否发生异常的语句

(2)作用

​ 将程序由异常状态转为正常流程。

(3)说明

  • as 子句是用于绑定错误对象的变量,可以省略
  • except子句可以有一个或多个,用来捕获某种类型的错误。
  • else子句最多只能有一个。
  • finally子句最多只能有一个,如果没有except子句,必须存在。
  • 如果异常没有被捕获到,会向上层(调用处)继续传递,直到程序终止运行。

11.3 raise 语句

(1) 作用

​ 抛出一个错误,让程序进入异常状态。

(2)目的

​ 在程序调用层数较深时,向主调函数传递错误信息要层层return比较麻烦,所以人为抛出异常,可以直接传递错误信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Wife:
def __init__(self, age):
self.age = age

@property
def age(self):
return self.__age

@age.setter
def age(self, value):
if 20 <= value <= 60:
self.__age = value
else:
# 创建异常 -- 抛出 错误信息
raise Exception("我不要","if 20 <= value <= 60",1001)

# -- 接收 错误信息
while True:
try:
age = int(input("请输入你老婆年龄:"))
w01 = Wife(age)
break
except Exception as e:
print(e.args) # ('我不要', 'if 30 <= value <= 60', 1001)

12. 迭代器iterator

​ 每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值。例如:循环获取容器中的元素。

12.1 可迭代对象iterabale

(1)定义

​ 可迭代对象:具有__iter__函数的对象,可以返回迭代器对象。如字符串,列表等。

(2)语法

1
2
3
4
5
6
7
# 创建:
class 可迭代对象名称:
def __iter__(self):
return 迭代器
# 使用:
for 变量名 in 可迭代对象:
语句

(3)原理

1
2
3
4
5
6
迭代器 = 可迭代对象.__iter__()
while True:
try:
print(迭代器.__next__())
except StopIteration:
break

(4)演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
message = "我是花果山水帘洞孙悟空"
# for item in message:
# print(item)

# 1. 获取迭代器对象
iterator = message.__iter__()
# 2. 获取下一个元素
while True:
try:
item = iterator.__next__()
print(item)
# 3. 如果停止迭代则跳出循环
except StopIteration:
break

12.2 迭代器对象iterator

(1)定义

​ 可以被next()函数调用并返回下一个值的对象。

(2)语法

1
2
3
4
5
6
7
8
class 迭代器类名:
def __init__(self, 聚合对象):
self.聚合对象= 聚合对象

def __next__(self):
if 没有元素:
raise StopIteration
return 聚合对象元素

(3)说明

​ 聚合对象通常是容器对象。

(4)作用

​ 使用者只需通过一种方式,便可简洁明了的获取聚合对象中各个元素,而又无需了解其内部结构。

(5)演示:

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
27
28
29
30
31
32
33
34
35
36
class StudentIterator: 
def __init__(self, data):
self.__data = data
self.__index = -1

def __next__(self):
if self.__index == len(self.__data) - 1:
raise StopIteration()
self.__index += 1
return self.__data[self.__index]


class StudentController:
def __init__(self):
self.__students = []

def add_student(self, stu):
self.__students.append(stu)

def __iter__(self):
return StudentIterator(self.__students)

controller = StudentController()
controller.add_student("悟空")
controller.add_student("八戒")
controller.add_student("唐僧")

# for item in controller:
# print(item) #
iterator = controller.__iter__()
while True:
try:
item = iterator.__next__()
print(item) #
except StopIteration:
break

13. 生成器generator

  • 定义:能够动态(循环一次计算一次返回一次)提供数据的可迭代对象。
  • 作用:在循环过程中,按照某种算法推算数据,不必创建容器存储完整的结果,从而节省内存空间。数据量越大,优势越明显。以上作用也称之为延迟操作或惰性操作,通俗的讲就是在需要的时候才计算结果,而不是一次构建出所有结果。

13.1 生成器函数

(1)定义

​ 含有yield语句的函数,返回值为生成器对象。

(2)语法

1
2
3
4
5
6
7
8
9
# 创建:
def 函数名():

yield 数据


# 调用:
for 变量名 in 函数名():
语句

(3)说明

  • 调用生成器函数将返回一个生成器对象,不执行函数体。
  • yield翻译为”产生”或”生成”

(4)执行过程:

​ a. 调用生成器函数会自动创建迭代器对象

​ b. 调用迭代器对象的__next__()方法时才执行生成器函数。

​ c. 每次执行到yield语句时返回数据,暂时离开。

​ d. 待下次调用__next__()方法时继续从离开处继续执行。

(5)原理

​ 生成迭代器对象的大致规则如下

​ a. 将yield关键字以前的代码放在next方法中。

​ b. 将yield关键字后面的数据作为next方法的返回值。

(6) 演示:

1
2
3
4
5
6
7
8
def my_range(stop):
number = 0
while number < stop:
yield number
number += 1

for number in my_range(5):
print(number) # 0 1 2 3 4

13.2 内置生成器

(1)枚举函数enumerate

  • 语法:
1
2
3
4
5
for 变量 in enumerate(可迭代对象):
语句

for 索引, 元素in enumerate(可迭代对象):
语句
  • 作用:遍历可迭代对象时,可以将索引与元素组合为一个元组
  • 演示:
1
2
3
4
5
6
7
8
9
10
11
12
13
list01 = [43, 43, 54, 56, 76]
# 从头到尾读 -- 读取数据
for item in list01:
print(item)

# 非从头到尾读 -- 修改数据
for i in range(len(list01)):
if list01[i] % 2 == 0:
list01[i] += 1

for i, item in enumerate(list01): # -- 读写数据
if item % 2 == 0:
list01[i] += 1

(2)zip

  • 语法:
1
2
for item in zip(可迭代对象1, 可迭代对象2):
语句
  • 作用:将多个可迭代对象中对应的元素组合成一个个元组,生成的元组个数由最小的可迭代对象决定。
  • 演示:
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
27
28
29
list_name = ["悟空", "八戒", "沙僧"]
list_age = [22, 26, 25]

# for 变量 in zip(可迭代对象1,可迭代对象2)
for item in zip(list_name, list_age):
print(item)
# ('悟空', 22)
# ('八戒', 26)
# ('沙僧', 25)

# 应用:矩阵转置
map = [
[2, 0, 0, 2],
[4, 2, 0, 2],
[2, 4, 2, 4],
[0, 4, 0, 4]
]
# new_map = []
# for item in zip(map[0],map[1],map[2],map[3]):
# new_map.append(list(item))
# print(new_map)

# new_map = []
# for item in zip(*map):
# new_map.append(list(item))

new_map = [list(item) for item in zip(*map)]
print(new_map)
# [[2, 4, 2, 0], [0, 2, 4, 4], [0, 0, 2, 0], [2, 2, 4, 4]]

13.3 生成器表达式

  • 定义:用推导式形式创建生成器对象。
  • 语法:
1
变量 = (表达式 for 变量 in 可迭代对象 if 条件)

14. 函数式编程

  • 定义:用一系列函数解决问题。

    • 函数可以赋值给变量,赋值后变量绑定函数。
    • 允许将函数作为参数传入另一个函数。
    • 允许函数返回一个函数
  • 高阶函数:将函数作为参数或返回值的函数。

14.1 函数作为参数

​ 将核心逻辑传入方法体,使该方法的适用性更广,体现了面向对象的开闭原则。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
list01 = [342, 4, 54, 56, 6776]

# 定义函数,在列表中查找所有大于100的数
def get_number_gt_100():
for number in list01:
if number > 100:
yield number


# 定义函数,在列表中查找所有偶数
def get_number_by_even():
for number in list01:
if number % 2 == 0:
yield number
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 参数:得到的是列表中的元素
# 返回值:对列表元素判断后的结果(True False)
def condition01(number):
return number > 100

def condition02(number):
return number % 2 == 0

def condition03(number):
return number < 10

# 通用函数
def find_all(condition): # 抽象
for item in list01:
# if number > 100:
# if condition01(item):
# if condition02(item):
if condition(item):# 统一
yield item

for item in find_all(condition03):
print(item)

(1)lambda 表达式

Python-lambda表达式

  • 定义:是一种匿名方法
  • 作用:

    • 作为参数传递时语法简洁,优雅,代码可读性强。
    • 随时创建和销毁,减少程序耦合度。
  • 语法

1
2
3
4
5
# 定义:
变量 = lambda 形参: 方法体

# 调用:
变量(实参)
  • 说明:
    • 形参没有可以不填
    • 方法体只能有一条语句,且不支持赋值语句。

(5) 演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from common.iterable_tools import IterableHelper

# 定义函数,在列表中查找所有大于100的数
# def condition01(number):
# return number > 100

# 定义函数,在列表中查找所有偶数
# def condition02(number):
# return number % 2 == 0

list01 = [342, 4, 54, 56, 6776]

for item in IterableHelper.find_all(list01,lambda number: number > 100):
print(item)

for item in IterableHelper.find_all(list01,lambda number: number % 2 == 0):
print(item)

(2)内置高阶函数

  • map(函数,可迭代对象):使用可迭代对象中的每个元素调用函数,将返回值作为新可迭代对象元素;返回值为新可迭代对象。
  • filter(函数,可迭代对象):根据条件筛选可迭代对象中的元素,返回值为新可迭代对象。
  • sorted(可迭代对象,key = 函数, reverse = bool值):排序,返回值为排序结果。
  • max(可迭代对象,key = 函数):根据函数获取可迭代对象的最大值。
  • min(可迭代对象,key = 函数):根据函数获取可迭代对象的最小值。
  • 演示:
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Employee:
def __init__(self, eid, did, name, money):
self.eid = eid # 员工编号
self.did = did # 部门编号
self.name = name
self.money = money


# 员工列表
list_employees = [
Employee(1001, 9002, "师父", 60000),
Employee(1002, 9001, "孙悟空", 50000),
Employee(1003, 9002, "猪八戒", 20000),
Employee(1004, 9001, "沙僧", 30000),
Employee(1005, 9001, "小白龙", 15000),
]

# 1. map 映射
# 需求:获取所有员工姓名
for item in map(lambda item: item.name, list_employees):
print(item)

# 2. filter 过滤器
# 需求:查找所有部门是9002的员工
for item in filter(lambda item: item.did == 9002, list_employees):
print(item.__dict__)

# 3. max min 最值
emp = max(list_employees, key=lambda emp: emp.money)
print(emp.__dict__)

# 4. sorted
# 升序排列
new_list = sorted(list_employees, key=lambda emp: emp.money)
print(new_list)

# 降序排列
new_list = sorted(list_employees, key=lambda emp: emp.money, reverse=True)
print(new_list)

14.2 函数作为返回值

逻辑连续,当内部函数被调用时,不脱离当前的逻辑。

(1)闭包

  • 三要素:

    • 必须有一个内嵌函数。
    • 内嵌函数必须引用外部函数中变量。
    • 外部函数返回值必须是内嵌函数。
  • 语法

1
2
3
4
5
6
7
8
9
10
# 定义:
def 外部函数名(参数):
外部变量
def 内部函数名(参数):
使用外部变量
return 内部函数名

# 调用:
变量 = 外部函数名(参数)
变量(参数)
  • 定义:是由函数及其相关的引用环境组合而成的实体。

  • 优点:内部函数可以使用外部变量。

  • 缺点:外部变量一直存在于内存中,不会在调用结束后释放,占用内存。
  • 作用:实现python装饰器。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 装饰器
    # 定义:
    def 函数装饰器名称(func):
    def 内部函数名(*args, **kwarg):
    需要添加的新功能
    return func
    return 内部函数名

    @ 函数装饰器名称
    def 被装饰函数名():
    原来的功能

(7) 演示:

1
2
3
4
5
6
7
8
9
10
11
def give_gife_money(money):  
print("获得", money, "元压岁钱")
def child_buy(commodity, price):
nonlocal money # 在内层函数修改外层嵌套函数内的变量
money -= price
print("购买了", commodity, "花了", price, "元,还剩下", money)
return child_buy

action = give_gife_money(500) # 返回值是一个函数child_buy
action("变形金刚", 200)
action("芭比娃娃", 300)

(2)函数装饰器decorator

  • 定义:在不改变原函数的调用以及内部代码情况下,为其添加新功能的函数。
  • 语法
1
2
3
4
5
6
7
8
9
10
11
def 函数装饰器名称(func):
def wrapper(*args, **kwargs): #星号元组形参,双星号字典形参
需要添加的新功能
return func(*args, **kwargs)
return wrapper

@ 函数装饰器名称
def 原函数名称(参数):
函数体

原函数(参数)
  • 本质:使用“@函数装饰器名称”修饰原函数,等同于创建与原函数名称相同的变量,关联内嵌函数;故调用原函数时执行内嵌函数。

原函数名称 = 函数装饰器名称(原函数名称)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def func01():
print("旧功能")


def new_func(func):
def wrapper():
print("新功能")
func() # 执行旧功能

return wrapper


# 新功能覆盖了旧功能(没有将func01作为参数传入new_func)
# func01 = new_func

# 调用一次外部函数(装饰器本质)
func01 = new_func(func01)
# 调用多次内部函数
func01()
func01()
  • 装饰器链:

    一个函数可以被多个装饰器修饰,执行顺序为从近到远。

15. 文件操作

15.1 文件与字节串

(1)文件

​ 文件是保存在持久化存储设备(硬盘、U盘、光盘..)上的一段数据。从格式编码角度分为文本文件(打开后会自动解码为字符)、二进制文件(视频、音频等)。在Python里把文件视作一种类型的对象,类似之前学习过的其它类型。

(2)字节串(bytes)

​ 在python3中引入了字节串的概念,与str不同,字节串以字节序列值表达数据,更方便用来处理二进程数据。因此在python3中字节串是常见的二进制数据展现方式。

  • 普通的ascii编码字符串可以在前面加b转换为字节串,例如:b’hello’
  • 字符串转换为字节串方法 :str.encode()
  • 字节串转换为字符串方法 : bytes.decode()

15.2 文件读写

(1)打开文件

  • file_object = open(file_name, access_mode=’r’, buffering=-1)

  • 功能:打开一个文件,返回一个文件对象。

  • 参数:

    • file_name 文件名;

    • access_mode 打开文件的方式,如果不写默认为‘r’

      文件模式 操作
      r 以读方式打开,文件必须存在
      w 以写方式打开,文件不存在则会创建,存在则清空原有内容
      a 以追加模式打开
      r+ 以读写模式打开,文件必须存在
      w+ 以读写模式打开,不存在则会创建,存在则清空原有内容
      a+ 以读写模式打开,追加模式
      rb 以二进制读模式打开,同r
      wb 以二进制写模式打开,同w
      ab 以二进制追加模式打开,同a
      rb+ 以二进制读写模式打开,同r+
      wb+ 以二进制读写模式打开,同w+
      ab+ 以二进制读写模式打开,同a+
    • buffering 1表示有行缓冲,默认则表示使用系统默认提供的缓冲机制。
    • 返回值:成功返回文件操作对象。
  • 注意

    • 加b的打开方式读写要求必须都是字节串
    • 无论什么文件都可以使用二进制方式打开,但是二进制文件使用文本方式打开读写会出错

(2)读取文件

  • read([size])

    • 功能: 来直接读取文件中字符。
    • 参数: 如果没有给定size参数(默认值为-1)或者size值为负,文件将被读取直至末尾,给定size最多读取给定数目个字符(字节)。
    • 返回值: 返回读取到的内容
    • 注意:文件过大时候不建议直接读取到文件结尾,读到文件结尾会返回空字符串。
  • readline([size])

    • 功能: 指定的用来读取文件中一行
    • 参数: 如果没有给定size参数(默认值为-1)或者size值为负,表示读取一行,给定size表示最多读取指定的字符(字节)。
    • 返回值: 返回读取到的内容
  • readlines([sizeint])

    • 功能: 读取文件中的每一行作为列表中的一项

    • 参数: 如果没有给定size参数(默认值为-1)或者size值为负,文件将被读取直至末尾,给定size表示读取到size字符所在行为止。

    • 返回值: 返回读取到的内容列表

    • 文件对象本身也是一个可迭代对象,在for循环中可以迭代文件的每一行。

1
2
for line in f:
print(line)

(3)写入文件

  • write(string)
    • 功能: 把文本数据或二进制数据块的字符串写入到文件中去
    • 参数:要写入的内容
    • 如果需要换行要自己在写入内容中添加\n
  • writelines(str_list)
    • 功能:接受一个字符串列表作为参数,将它们写入文件。
    • 参数: 要写入的内容列表

(4)关闭文件

​ 打开一个文件后我们就可以通过文件对象对文件进行操作了,当操作结束后使用close()关闭这个对象可以防止一些误操作,也可以节省资源。

​ file_object.close()

(5)with操作

  • Python中with语句使用于对资源进行访问的场合,保证不管处理过程中是否发生错误或者异常都会

    执行规定的”清理“操作,释放被访问的资源,比如有文件读写后自动关闭、线程中锁自动获取和释放。

1
2
with context_expression [as target(s)]:
with-body
  • 通过with方法可以不用close(),因为with生成的对象在语句块结束后会自动处理,所以也就不需要close了,但是这个文件对象只能在with语句块内使用。
1
2
with open('file','r+') as f:
f.read()

15.2 其他操作

(1)刷新缓冲区

  • 缓冲:系统自动的在内存中为每一个正在使用的文件开辟一个缓冲区,从内存向磁盘输出数据必须先送到内存缓冲区,再由缓冲区送到磁盘中去。从磁盘中读数据,则一次从磁盘文件将一批数据读入到内存缓冲区中,然后再从缓冲区将数据送到程序的数据区。
  • 刷新缓冲区条件:

    • 缓冲区被写满

    • 程序执行结束或者文件对象被关闭

    • 行缓冲遇到换行

    • 程序中调用flush()函数

      • flush()
        该函数调用后会进行一次磁盘交互,将缓冲区中的内容写入到磁盘。

(2)文件偏移量

  • 定义:打开一个文件进行操作时系统会自动生成一个记录,记录中描述了我们对文件的一系列操作。其中包括每次操作到的文件位置。文件的读写操作都是从这个位置开始进行的。

  • 基本操作

  • tell()

    • 功能:获取文件偏移量大小
  • seek(offset[,whence])

    • 功能:移动文件偏移量位置
    • 参数:
      • offset 代表相对于某个位置移动的字节数。负数表示向前移动,正数表示向后移动。
      • whence是基准位置的默认值为 0,代表从文件开头算起,1代表从当前位置算起,2 代表从文件末尾算起。
      • 必须以二进制方式打开文件时基准位置才能是1或者2

(3)文件描述符

  • 定义:系统中每一个IO操作都会分配一个整数作为编号,该整数即这个IO操作的文件描述符。

  • 获取文件描述符

    • fileno()
      通过IO对象获取对应的文件描述符

15.3 文件管理函数

  • 获取文件大小

    os.path.getsize(file)

  • 查看文件列表

    os.listdir(dir)

  • 查看文件是否存在

    os.path.exists(file)

  • 判断文件类型

    os.path.isfile(file)

  • 删除文件

    os.remove(file)

16. 网络编程

计算机网络功能主要包括实现资源共享,实现数据信息的快速传递。

16.1 网络编程基础

(1)OSI七层模型

制定组织: ISO(国际标准化组织)

作用:使网络通信工作流程标准化

应用层 : 提供用户服务,具体功能由应用程序实现
表示层 : 数据的压缩优化加密
会话层 : 建立用户级的连接,选择适当的传输服务
传输层 : 提供传输服务
网络层 : 路由选择,网络互联
链路层 : 进行数据交换,控制具体数据的发送
物理层 : 提供数据传输的硬件保证,网卡接口,传输介质

优点

  1. 建立了统一的工作流程
  2. 分部清晰,各司其职,每个步骤分工明确
  3. 降低了各个模块之间的耦合度,便于开发

(2)四层模型(TCP/IP模型)

背景 : 实际工作中工程师无法完全按照七层模型要求操作,逐渐演化为更符合实际情况的四层

TCP/IP模型

1> 数据传输过程
  1. 发送端由应用程序发送消息,逐层添加首部信息,最终在物理层发送消息包。
  2. 发送的消息经过多个节点(交换机,路由器)传输,最终到达目标主机。
  3. 目标主机由物理层逐层解析首部消息包,最终到应用程序呈现消息。

TCP/IP模型

2> 网络协议

在网络数据传输中,都遵循的规定,包括建立什么样的数据结构,什么样的特殊标志等。

3> 网络基础概念
  • IP地址

功能:确定一台主机的网络路由位置

查看本机网络地址命令: ifconfig

结构

IPv4 点分十进制表示 172.40.91.185 每部分取值范围0–255
IPv6 128位 扩大了地址范围

  • 域名

定义: 给网络服务器地址起的名字

作用: 方便记忆,表达一定的含义

ping [ip] : 测试和某个主机是否联通

  • 端口号(port)

作用:端口是网络地址的一部分,用于区分主机上不同的网络应用程序。

特点:一个系统中的应用监听端口不能重复

取值范围: 1 – 65535

1–1023 系统应用或者大众程序监听端口
1024–65535 自用端口

(3)传输层服务

1> 基于TCP协议的数据传输
  1. 传输特征 : 提供了可靠的数据传输,可靠性指数据传输过程中无丢失,无失序,无差错,无重复。
  2. 实现手段 : 在通信前需要建立数据连接,通信结束要正常断开连接。
  3. 适用情况 : 对数据传输准确性有明确要求,传数文件较大,需要确保可靠性的情况。比如:网页获取,文件下载,邮件收发。

三次握手(建立连接)

客户端向服务器发送消息报文请求连接
服务器收到请求后,回复报文确定可以连接
客户端收到回复,发送最终报文连接建立

四次挥手(断开连接)

主动方发送报文请求断开连接
被动方收到请求后,立即回复,表示准备断开
被动方准备就绪,再次发送报文表示可以断开
主动方收到确定,发送最终报文完成断开

2 > 基于UDP协议的数据传输
  1. 传输特点 : 不保证传输的可靠性,传输过程没有连接和断开,数据收发自由随意。
  2. 适用情况 : 网络较差,对传输可靠性要求不高。比如:网络视频,群聊,广播

16.2 socket套接字编程

(1)套接字介绍

  1. 套接字 : 实现网络编程进行数据传输的一种技术手段

  2. Python实现套接字编程:import socket

  3. 套接字分类

  • 流式套接字(SOCK_STREAM): 以字节流方式传输数据,实现TCP网络传输方案。(面向连接–tcp协议–可靠的–流式套接字)
  • 数据报套接字(SOCK_DGRAM): 以数据报形式传输数据,实现UDP网络传输方案。(无连接–udp协议–不可靠–数据报套接字)

(2)tcp套接字编程

1> 服务端流程

  1. 创建套接字
1
2
3
4
5
6
7
sockfd=socket.socket(socket_family=AF_INET,socket_type=SOCK_STREAM,proto=0)
功能:创建套接字
参数:
socket_family 网络地址类型 AF_INET表示ipv4
socket_type 套接字类型 SOCK_STREAM(流式) SOCK_DGRAM(数据报式)
proto 通常为0 选择子协议
返回值: 套接字对象
  1. 绑定地址

本地地址 : ‘localhost’ , ‘127.0.0.1’
网络地址 : ‘172.40.91.185’
自动获取地址: ‘0.0.0.0’

1
2
3
sockfd.bind(addr)
功能: 绑定本机网络地址
参数: 二元元组 (ip,port) ('0.0.0.0',8888)
  1. 设置监听
1
2
3
sockfd.listen(n)
功能 : 将套接字设置为监听套接字,确定监听队列大小
参数 : 监听队列大小
  1. 等待处理客户端连接请求
1
2
3
4
connfd,addr = sockfd.accept()
功能: 阻塞等待处理客户端请求
返回值: connfd 客户端连接套接字
addr 连接的客户端地址
  1. 消息收发
1
2
3
4
5
6
7
8
9
data = connfd.recv(buffersize)
功能 : 接受客户端消息
参数 :每次最多接收消息的大小
返回值: 接收到的内容

n = connfd.send(data)
功能 : 发送消息
参数 :要发送的内容 bytes格式
返回值: 发送的字节数
  1. 关闭套接字
1
2
sockfd.close()
功能:关闭套接字
2> 客户端流程

  1. 创建套接字

注意:只有相同类型的套接字才能进行通信

  1. 请求连接
1
2
3
sockfd.connect(server_addr)
功能:连接服务器
参数:元组 服务器地址
  1. 收发消息

注意: 防止两端都阻塞,recv send要配合

  1. 关闭套接字
3> tcp 套接字数据传输特点
  • tcp连接中当一端退出,另一端如果阻塞在recv,此时recv会立即返回一个空字串。

  • tcp连接中如果一端已经不存在,仍然试图通过send发送则会产生BrokenPipeError

  • 一个监听套接字可以同时连接多个客户端,也能够重复被连接

4> 网络收发缓冲区
  1. 网络缓冲区有效的协调了消息的收发速度
  2. send和recv实际是向缓冲区发送接收消息,当缓冲区不为空recv就不会阻塞。
5> tcp粘包

原因:tcp以字节流方式传输,没有消息边界。多次发送的消息被一次接收,此时就会形成粘包。

影响:如果每次发送内容是一个独立的含义,需要接收端独立解析此时粘包会有影响。

处理方法

  1. 人为的添加消息边界
  2. 控制发送速度

(3)UDP套接字编程

1> 服务端流程

  1. 创建数据报套接字
1
sockfd = socket(AF_INET,SOCK_DGRAM)
  1. 绑定地址
1
sockfd.bind(addr)
  1. 消息收发
1
2
3
4
5
6
7
8
9
10
11
12
data,addr = sockfd.recvfrom(buffersize)
功能: 接收UDP消息
参数: 每次最多接收多少字节
返回值: data 接收到的内容
addr 消息发送方地址

n = sockfd.sendto(data,addr)
功能: 发送UDP消息
参数: data 发送的内容 bytes格式
addr 目标地址
返回值:发送的字节数

  1. 关闭套接字
1
sockfd.close()
2> 客户端流程

  1. 创建套接字
  2. 收发消息
  3. 关闭套接字

总结 :tcp套接字和udp套接字编程区别

  1. 流式套接字是以字节流方式传输数据,数据报套接字以数据报形式传输
  2. tcp套接字会有粘包,udp套接字有消息边界不会粘包
  3. tcp套接字保证消息的完整性,udp套接字则不能
  4. tcp套接字依赖listen accept建立连接才能收发消息,udp套接字则不需要
  5. tcp套接字使用send,recv收发消息,udp套接字使用sendto,recvfrom

(4)socket套接字属性

  • sockfd.type 套接字类型

  • sockfd.family 套接字地址类型

  • sockfd.getsockname() 获取套接字绑定地址

  • sockfd.fileno() 获取套接字的文件描述符
  • sockfd.getpeername() 获取连接套接字客户端地址
  • sockfd.setsockopt(level,option,value)
  • 功能:设置套接字选项
  • 参数: level 选项类别 SOL_SOCKET
            option 具体选项内容
                  value  选项值
    

16.3 struct模块进行数据打包

  1. 原理: 将一组简单数据进行打包,转换为bytes格式发送。或者将一组bytes格式数据,进行解析。
  2. 接口使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Struct(fmt)
功能: 生成结构化对象
参数:fmt 定制的数据结构

st.pack(v1,v2,v3....)
功能: 将一组数据按照指定格式打包转换为bytes
参数:要打包的数据
返回值: bytes字节串

st.unpack(bytes_data)
功能: 将bytes字节串按照指定的格式解析
参数: 要解析的 bytes字节串
返回值: 解析后的内容

struct.pack(fmt,v1,v2,v3...)
struct.unpack(fmt,bytes_data)

说明: 可以使用struct模块直接调用pack unpack。此时这两函数第一个参数传入fmt。其他用法功能相同

16.4 HTTP传输

(1)HTTP协议 (超文本传输协议)

  1. 用途 : 网页获取,数据的传输

  2. 特点

  • 应用层协议,传输层使用tcp传输
  • 简单,灵活,很多语言都有HTTP专门接口
  • 无状态,协议不记录传输内容
  • http1.1 支持持久连接,丰富了请求类型
  1. 网页请求过程

1.客户端(浏览器)通过tcp传输,发送http请求给服务端
2.服务端接收到http请求后进行解析
3.服务端处理请求内容,组织响应内容
4.服务端将响应内容以http响应格式发送给浏览器
5.浏览器接收到响应内容,解析展示

(2)HTTP请求(request)

  • 请求行 : 具体的请求类别和请求内容
1
2
GET         /        HTTP/1.1
请求类别 请求内容 协议版本
1
2
3
4
5
6
7
8
9
10
请求类别:每个请求类别表示要做不同的事情 
GET : 获取网络资源
POST :提交一定的信息,得到反馈
HEAD : 只获取网络资源的响应头
PUT : 更新服务器资源
DELETE : 删除服务器资源
CONNECT
TRACE : 测试
OPTIONS : 获取服务器性能信息

  • 请求头:对请求的进一步解释和描述
1
Accept-Encoding: gzip
  • 空行
  • 请求体: 请求参数或者提交内容

(3)http响应(response)

  1. 响应格式:响应行,响应头,空行,响应体
  • 响应行 : 反馈基本的响应情况
1
2
HTTP/1.1     200       OK
版本信息 响应码 附加信息
1
2
3
4
5
6
响应码 : 
1xx 提示信息,表示请求被接收
2xx 响应成功
3xx 响应需要进一步操作,重定向
4xx 客户端错误
5xx 服务器错误
  • 响应头:对响应内容的描述
1
Content-Type: text/html
  • 响应体:响应的主体内容信息

17. 并发编程

17.1 多任务编程

  1. 意义: 充分利用计算机CPU的多核资源,同时处理多个应用程序任务,以此提高程序的运行效率。
  2. 实现方案 :多进程 , 多线程

17.2 进程(process)

(1)进程理论基础

  1. 定义 : 程序在计算机中的一次运行。
    • 程序是一个可执行的文件,是静态的占有磁盘。
    • 进程是一个动态的过程描述,占有计算机运行资源,有一定的生命周期。
  1. 系统中如何产生一个进程

    【1】 用户空间通过调用程序接口或者命令发起请求
    【2】 操作系统接收用户请求,开始创建进程
    【3】 操作系统调配计算机资源,确定进程状态等
    【4】 操作系统将创建的进程提供给用户使用

  1. 进程基本概念
  • cpu时间片:如果一个进程占有cpu内核则称这个进程在cpu时间片上。

  • PCB(进程控制块):在内存中开辟的一块空间,用于存放进程的基本信息,也用于系统查找识别进程。

  • 进程ID(PID): 系统为每个进程分配的一个大于0的整数,作为进程ID。每个进程ID不重复。

    Linux查看进程ID : ps -aux

  • 父子进程 : 系统中每一个进程(除了系统初始化进程)都有唯一的父进程,可以有0个或多个子进程。父子进程关系便于进程管理。

    查看进程树: pstree

  • 进程状态

    • 三态
      就绪态 : 进程具备执行条件,等待分配cpu资源
      运行态 : 进程占有cpu时间片正在运行
      等待态 : 进程暂时停止运行,让出cpu

  • 五态 (在三态基础上增加新建和终止)
    新建 : 创建一个进程,获取资源的过程
    终止 : 进程结束,释放资源的过程

  • 状态查看命令 : ps -aux –> STAT列
           S 等待态
           R 执行态
           Z 僵尸

           \+ 前台进程
           l   有多线程的
  • 进程的运行特征
    【1】 多进程可以更充分使用计算机多核资源
    【2】 进程之间的运行互不影响,各自独立
    【3】 每个进程拥有独立的空间,各自使用自己空间资源

17.3 基于fork的多进程编程

(1)fork使用

pid = os.fork()
功能: 创建新的进程
返回值:整数,如果创建进程失败返回一个负数,如果成功则在原有进程中返回新进程的PID,在新进程中返回0

注意

  • 子进程会复制父进程全部内存空间,从fork下一句开始执行。
  • 父子进程各自独立运行,运行顺序不一定。
  • 利用父子进程fork返回值的区别,配合if结构让父子进程执行不同的内容几乎是固定搭配。
  • 父子进程有各自特有特征比如PID PCB 命令集等。
  • 父进程fork之前开辟的空间子进程同样拥有,父子进程对各自空间的操作不会相互影响。

(2)进程相关函数

os.getpid()
功能: 获取一个进程的PID值
返回值: 返回当前进程的PID

os.getppid()
功能: 获取父进程的PID号
返回值: 返回父进程PID

os._exit(status)
功能: 结束一个进程
参数:进程的终止状态

sys.exit([status])
功能:退出进程
参数:整数 表示退出状态
字符串 表示退出时打印内容

(3)孤儿和僵尸

  1. 孤儿进程 : 父进程先于子进程退出,此时子进程成为孤儿进程。

特点: 孤儿进程会被系统进程收养,此时系统进程就会成为孤儿进程新的父进程,孤儿进程退出该进程会自动处理。

  1. 僵尸进程 : 子进程先于父进程退出,父进程又没有处理子进程的退出状态,此时子进程就会称为僵尸进程。

特点: 僵尸进程虽然结束,但是会存留部分PCB在内存中,大量的僵尸进程会浪费系统的内存资源。

  1. 如何避免僵尸进程产生
  • 使用wait函数处理子进程退出
1
2
3
4
pid,status = os.wait()
功能:在父进程中阻塞等待处理子进程退出
返回值: pid 退出的子进程的PID
status 子进程退出状态
  • 创建二级子进程处理僵尸

    【1】 父进程创建子进程,等待回收子进程
    【2】 子进程创建二级子进程然后退出
    【3】 二级子进程称为孤儿,和原来父进程一同执行事件

  • 通过信号处理子进程退出

    原理: 子进程退出时会发送信号给父进程,如果父进程忽略子进程信号,则系统就会自动处理子进程退出。

    方法: 使用signal模块在父进程创建子进程前写如下语句 :

    1
    2
    import signal
    signal.signal(signal.SIGCHLD,signal.SIG_IGN)

    特点 : 非阻塞,不会影响父进程运行。可以处理所有子进程退出

(4)群聊聊天室

功能 : 类似qq群功能
【1】 有人进入聊天室需要输入姓名,姓名不能重复
【2】 有人进入聊天室时,其他人会收到通知:xxx 进入了聊天室
【3】 一个人发消息,其他人会收到:xxx : xxxxxxxxxxx
【4】 有人退出聊天室,则其他人也会收到通知:xxx退出了聊天室
【5】 扩展功能:服务器可以向所有用户发送公告:管理员消息: xxxxxxxxx

17.4 multiprocessing 模块创建进程

(1)进程创建方法

  1. 流程特点

    【1】 将需要子进程执行的事件封装为函数
    【2】 通过模块的Process类创建进程对象,关联函数
    【3】 可以通过进程对象设置进程信息及属性
    【4】 通过进程对象调用start启动进程
    【5】 通过进程对象调用join回收进程

  2. 基本接口使用
1
2
3
4
5
Process()
功能 : 创建进程对象
参数 : target 绑定要执行的目标函数
args 元组,用于给target函数位置传参
kwargs 字典,给target函数键值传参
1
2
p.start()
功能 : 启动进程

注意:启动进程此时target绑定函数开始执行,该函数作为子进程执行内容,此时进程真正被创建

1
2
3
p.join([timeout])
功能:阻塞等待回收进程
参数:超时时间

注意

  • 使用multiprocessing创建进程同样是子进程复制父进程空间代码段,父子进程运行互不影响。
  • 子进程只运行target绑定的函数部分,其余内容均是父进程执行内容。
  • multiprocessing中父进程往往只用来创建子进程、回收子进程,具体事件由子进程完成。
  • multiprocessing创建的子进程中无法使用标准输入
  1. 进程对象属性

p.name 进程名称

p.pid 对应子进程的PID号

p.is_alive() 查看子进程是否在生命周期

p.daemon 设置父子进程的退出关系

  • 如果设置为True则子进程会随父进程的退出而结束
  • 要求必须在start()前设置
  • 如果daemon设置成True 通常就不会使用 join()

(2)自定义进程类

  1. 创建步骤

    【1】 继承Process类
    【2】 重写__init__方法添加自己的属性,使用super()加载父类属性
    【3】 重写run()方法

  2. 使用方法

    【1】 实例化对象
    【2】 调用start自动执行run方法
    【3】 调用join回收进程

(3)进程池实现

  1. 必要性

    【1】 进程的创建和销毁过程消耗的资源较多
    【2】 当任务量众多,每个任务在很短时间内完成时,需要频繁的创建和销毁进程。此时对计算机压力较大
    【3】 进程池技术很好的解决了以上问题。

  2. 原理

创建一定数量的进程来处理事件,事件处理完进程不退出而是继续处理其他事件,直到所有事件全都处理完毕统一销毁。增加进程的重复利用,降低资源消耗。

  1. 进程池实现过程

【1】 创建进程池对象,放入适当的进程

1
2
3
4
5
from multiprocessing import Pool

Pool(processes)
功能: 创建进程池对象
参数: 指定进程数量,默认根据系统自动判定

【2】 将事件加入进程池队列执行

1
2
3
4
5
6
pool.apply_async(func,args,kwds)
功能: 使用进程池执行func事件
参数: func 事件函数
args 元组 给func按位置传参
kwds 字典 给func按照键值传参
返回值: 返回函数事件对象

【3】 关闭进程池

1
2
pool.close()
功能: 关闭进程池

【4】 回收进程池中进程

1
2
pool.join()
功能: 回收进程池中进程

17.5 进程间通信(IPC)

  1. 必要性: 进程间空间独立,资源不共享,此时在需要进程间数据传输时就需要特定的手段进行数据通信。
  1. 常用进程间通信方法

管道 消息队列 共享内存 信号 信号量 套接字

(1)管道通信(Pipe)

  1. 通信原理

在内存中开辟管道空间,生成管道操作对象,多个进程使用同一个管道对象进行读写即可实现通信

  1. 实现方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from  multiprocessing import Pipe

fd1,fd2 = Pipe(duplex = True)
功能: 创建管道
参数:默认表示双向管道
如果为False 表示单向管道
返回值:表示管道两端的读写对象
如果是双向管道均可读写
如果是单向管道fd1只读 fd2只写

fd.recv()
功能 : 从管道获取内容
返回值:获取到的数据

fd.send(data)
功能: 向管道写入内容
参数: 要写入的数据

(2)消息队列

  1. 通信原理

在内存中建立队列模型,进程通过队列将消息存入,或者从队列取出完成进程间通信。

  1. 实现方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from multiprocessing import Queue

q = Queue(maxsize=0)
功能: 创建队列对象
参数:最多存放消息个数
返回值:队列对象

q.put(data,[block,timeout])
功能:向队列存入消息
参数:data 要存入的内容
block 设置是否阻塞 False为非阻塞
timeout 超时检测

q.get([block,timeout])
功能:从队列取出消息
参数:block 设置是否阻塞 False为非阻塞
timeout 超时检测
返回值: 返回获取到的内容

q.full() 判断队列是否为满
q.empty() 判断队列是否为空
q.qsize() 获取队列中消息个数
q.close() 关闭队列

(3)共享内存

  1. 通信原理:

    在内中开辟一块空间,进程可以写入内容和读取内容完成通信,但是每次写入内容会覆盖之前内容。

  2. 实现方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from multiprocessing import Value,Array

obj = Value(ctype,data)
功能 : 开辟共享内存
参数 : ctype 表示共享内存空间类型 'i' 'f' 'c'
data 共享内存空间初始数据
返回值:共享内存对象

obj.value 对该属性的修改查看即对共享内存读写


obj = Array(ctype,data)
功能: 开辟共享内存空间
参数: ctype 表示共享内存数据类型
data 整数则表示开辟空间的大小,其他数据类型表示开辟空间存放的初始化数据
返回值:共享内存对象

Array共享内存读写: 通过遍历obj可以得到每个值,直接可以通过索引序号修改任意值。

* 可以使用obj.value直接打印共享内存中的字节串

(4)信号量(信号灯集)

  1. 通信原理

给定一个数量对多个进程可见。多个进程都可以操作该数量增减,并根据数量值决定自己的行为。

  1. 实现方法
1
2
3
4
5
6
7
8
9
10
from multiprocessing import Semaphore

sem = Semaphore(num)
功能 : 创建信号量对象
参数 : 信号量的初始值
返回值 : 信号量对象

sem.acquire() 将信号量减1 当信号量为0时阻塞
sem.release() 将信号量加1
sem.get_value() 获取信号量数量

17.6 线程编程(Thread)

(1)线程基本概念

  1. 什么是线程
    【1】 线程被称为轻量级的进程
    【2】 线程也可以使用计算机多核资源,是多任务编程方式
    【3】 线程是系统分配内核的最小单元
    【4】 线程可以理解为进程的分支任务

  2. 线程特征
    【1】 一个进程中可以包含多个线程
    【2】 线程也是一个运行行为,消耗计算机资源
    【3】 一个进程中的所有线程共享这个进程的资源
    【4】 多个线程之间的运行互不影响各自运行
    【5】 线程的创建和销毁消耗资源远小于进程
    【6】 各个线程也有自己的ID等特征

(2)threading模块创建线程

【1】 创建线程对象

1
2
3
4
5
6
7
from threading import Thread 

t = Thread()
功能:创建线程对象
参数:target 绑定线程函数
args 元组 给线程函数位置传参
kwargs 字典 给线程函数键值传参

【2】 启动线程

1
t.start()

【3】 回收线程

1
t.join([timeout])

(3)线程对象属性

t.name 线程名称
t.setName() 设置线程名称
t.getName() 获取线程名称

t.is_alive() 查看线程是否在生命周期

t.daemon 设置主线程和分支线程的退出关系
t.setDaemon() 设置daemon属性值
t.isDaemon() 查看daemon属性值

daemon为True时主线程退出分支线程也退出。要在start前设置,通常不和join一起使用。

(4)自定义线程类

  1. 创建步骤
    【1】 继承Thread类
    【2】 重写__init__方法添加自己的属性,使用super()加载父类属性
    【3】 重写run()方法

  2. 使用方法
    【1】 实例化对象
    【2】 调用start自动执行run方法
    【3】 调用join回收线程

17.7 同步互斥

(1)线程间通信方法

  1. 通信方法

线程间使用全局变量进行通信

  1. 共享资源争夺
  • 共享资源:多个进程或者线程都可以操作的资源称为共享资源。对共享资源的操作代码段称为临界区

  • 影响 : 对共享资源的无序操作可能会带来数据的混乱,或者操作错误。此时往往需要同步互斥机制协调操作顺序

  1. 同步互斥机制

同步 : 同步是一种协作关系,为完成操作,多进程或者线程间形成一种协调,按照必要的步骤有序执行操作

互斥 : 互斥是一种制约关系,当一个进程或者线程占有资源时会进行加锁处理,此时其他进程线程就无法操作该资源,直到解锁后才能操作。

(2)线程同步互斥方法

1> 线程Event
1
2
3
4
5
6
7
8
9
10
11
from threading import Event

e = Event() 创建线程event对象

e.wait([timeout]) 阻塞等待e被set

e.set() 设置e,使wait结束阻塞

e.clear() 使e回到未被设置状态

e.is_set() 查看当前e是否被设置
2> 线程锁 Lock
1
2
3
4
5
6
7
8
9
10
from  threading import Lock

lock = Lock() 创建锁对象
lock.acquire() 上锁 如果lock已经上锁再调用会阻塞
lock.release() 解锁

with lock: 上锁
...
...
with代码块结束自动解锁

(3)死锁及其处理

  1. 定义

死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。

  1. 死锁产生条件

死锁发生的必要条件

  • 互斥条件:指线程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
  • 请求和保持条件:指线程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求线程阻塞,但又对自己已获得的其它资源保持不放
  • 不剥夺条件:指线程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放,通常CPU内存资源是可以被系统强行调配剥夺的。
  • 环路等待条件:指在发生死锁时,必然存在一个线程——资源的环形链,即进程集合{T0,T1,T2,···,Tn}中的T0正在等待一个T1占用的资源;T1正在等待T2占用的资源,……,Tn正在等待已被T0占用的资源。

死锁的产生原因

简单来说造成死锁的原因可以概括成三句话:

  • 当前线程拥有其他线程需要的资源
  • 当前线程等待其他线程已拥有的资源
  • 都不放弃自己拥有的资源
  1. 如何避免死锁

    死锁是我们非常不愿意看到的一种现象,我们要尽可能避免死锁的情况发生。通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或者几个,来预防发生死锁。预防死锁是一种较易实现的方法。但是由于所施加的限制条件往往太严格,可能会导致系统资源利用率。

17.8 python线程GIL

  1. python线程的GIL问题 (全局解释器锁)

什么是GIL :由于python解释器设计中加入了解释器锁,导致python解释器同一时刻只能解释执行一个线程,大大降低了线程的执行效率。

导致后果: 因为遇到阻塞时线程会主动让出解释器,去解释其他线程。所以python多线程在执行多阻塞高延迟IO时可以提升程序效率,其他情况并不能对效率有所提升。

GIL问题建议

  • 尽量使用进程完成无阻塞的并发行为
  • 不使用c作为解释器 (Java C#)
  1. 结论 : 在无阻塞状态下,多线程程序和单线程程序执行效率几乎差不多,甚至还不如单线程效率。但是多进程运行相同内容却可以有明显的效率提升。

17.9 进程线程的区别联系

(1)区别联系

  1. 两者都是多任务编程方式,都能使用计算机多核资源
  2. 进程的创建删除消耗的计算机资源比线程多
  3. 进程空间独立,数据互不干扰,有专门通信方法;线程使用全局变量通信
  4. 一个进程可以有多个分支线程,两者有包含关系
  5. 多个线程共享进程资源,在共享资源操作时往往需要同步互斥处理
  6. 进程线程在系统中都有自己的特有属性标志,如ID,代码段,命令集等。

(2)使用场景

  1. 任务场景:如果是相对独立的任务模块,可能使用多进程,如果是多个分支共同形成一个整体任务可能用多线程

  2. 项目结构:多种编程语言实现不同任务模块,可能是多进程,或者前后端分离应该各自为一个进程。

  3. 难易程度:通信难度,数据处理的复杂度来判断用进程间通信还是同步互斥方法。

(3)要求

  1. 对进程线程怎么理解/说说进程线程的差异
  2. 进程间通信知道哪些,有什么特点
  3. 什么是同步互斥,你什么情况下使用,怎么用
  4. 给一个情形,说说用进程还是线程,为什么
  5. 问一些概念,僵尸进程的处理,GIL问题,进程状态

17.10 并发网络通信模型

(1)常见网络模型

  1. 循环服务器模型 :循环接收客户端请求,处理请求。同一时刻只能处理一个请求,处理完毕后再处理下一个。

优点:实现简单,占用资源少
缺点:无法同时处理多个客户端请求

适用情况:处理的任务可以很快完成,客户端无需长期占用服务端程序。udp比tcp更适合循环。

  1. 多进程/线程网络并发模型:每当一个客户端连接服务器,就创建一个新的进程/线程为该客户端服务,客户端退出时再销毁该进程/线程。

优点:能同时满足多个客户端长期占有服务端需求,可以处理各种请求。
缺点: 资源消耗较大

适用情况:客户端同时连接量较少,需要处理行为较复杂情况。

  1. IO并发模型:利用IO多路复用,异步IO等技术,同时处理多个客户端IO请求。

    优点 : 资源消耗少,能同时高效处理多个IO行为
    缺点 : 只能处理并发产生的IO事件,无法处理cpu计算

    适用情况:HTTP请求,网络传输等都是IO行为。

(2)基于fork的多进程网络并发模型

实现步骤

  1. 创建监听套接字
  2. 等待接收客户端请求
  3. 客户端连接创建新的进程处理客户端请求
  4. 原进程继续等待其他客户端连接
  5. 如果客户端退出,则销毁对应的进程

(3)基于threading的多线程网络并发

实现步骤

  1. 创建监听套接字
  2. 循环接收客户端连接请求
  3. 当有新的客户端连接创建线程处理客户端请求
  4. 主线程继续等待其他客户端连接
  5. 当客户端退出,则对应分支线程退出

(4)ftp 文件服务器

  1. 功能
    【1】 分为服务端和客户端,要求可以有多个客户端同时操作。
    【2】 客户端可以查看服务器文件库中有什么文件。
    【3】 客户端可以从文件库中下载文件到本地。
    【4】 客户端可以上传一个本地文件到文件库。
    【5】 使用print在客户端打印命令输入提示,引导操作

17.11 IO并发

(1)IO 分类

IO分类:阻塞IO ,非阻塞IO,IO多路复用,异步IO等

1> 阻塞IO
  1. 定义:在执行IO操作时如果执行条件不满足则阻塞。阻塞IO是IO的默认形态。

  2. 效率:阻塞IO是效率很低的一种IO。但是由于逻辑简单所以是默认IO行为。

  3. 阻塞情况:

  • 因为某种执行条件没有满足造成的函数阻塞
    e.g. accept input recv

  • 处理IO的时间较长产生的阻塞状态
    e.g. 网络传输,大文件读写

2> 非阻塞IO
  1. 定义 :通过修改IO属性行为,使原本阻塞的IO变为非阻塞的状态。
  • 设置套接字为非阻塞IO

    sockfd.setblocking(bool)
    功能:设置套接字为非阻塞IO
    参数:默认为True,表示套接字IO阻塞;设置为False则套接字IO变为非阻塞

  • 超时检测 :设置一个最长阻塞时间,超过该时间后则不再阻塞等待。

    sockfd.settimeout(sec)
    功能:设置套接字的超时时间
    参数:设置的时间

(2)IO多路复用

  1. 定义

    同时监控多个IO事件,当哪个IO事件准备就绪就执行哪个IO事件。以此形成可以同时处理多个IO的行为,避免一个IO阻塞造成其他IO均无法执行,提高了IO执行效率。

  2. 具体方案

    select方法 : windows linux unix
    poll方法: linux unix
    epoll方法: linux

1> select 方法
1
2
3
4
5
6
7
8
9
10
rs, ws, xs=select(rlist, wlist, xlist[, timeout])
功能: 监控IO事件,阻塞等待IO发生
参数:rlist 列表 存放关注的等待发生的IO事件
wlist 列表 存放关注的要主动处理的IO事件
xlist 列表 存放关注的出现异常要处理的IO
timeout 超时时间

返回值: rs 列表 rlist中准备就绪的IO
ws 列表 wlist中准备就绪的IO
xs 列表 xlist中准备就绪的IO

select 实现tcp服务

【1】 将关注的IO放入对应的监控类别列表
【2】通过select函数进行监控
【3】遍历select返回值列表,确定就绪IO事件
【4】处理发生的IO事件

注意

wlist中如果存在IO事件,则select立即返回给ws
处理IO过程中不要出现死循环占有服务端的情况
IO多路复用消耗资源较少,效率较高

2> poll方法

代码实现: day12/poll_server.py

1
2
3
p = select.poll()
功能 : 创建poll对象
返回值: poll对象
1
2
3
4
5
6
7
8
9
10
11
12
13
p.register(fd,event)   
功能: 注册关注的IO事件
参数:fd 要关注的IO
event 要关注的IO事件类型
常用类型:POLLIN 读IO事件(rlist)
POLLOUT 写IO事件 (wlist)
POLLERR 异常IO (xlist)
POLLHUP 断开连接
e.g. p.register(sockfd,POLLIN|POLLERR)

p.unregister(fd)
功能:取消对IO的关注
参数:IO对象或者IO对象的fileno
1
2
3
4
5
events = p.poll()
功能: 阻塞等待监控的IO事件发生
返回值: 返回发生的IO
events格式 [(fileno,event),()....]
每个元组为一个就绪IO,元组第一项是该IO的fileno,第二项为该IO就绪的事件类型

poll_server 步骤

【1】 创建套接字
【2】 将套接字register
【3】 创建查找字典,并维护
【4】 循环监控IO发生
【5】 处理发生的IO
3> epoll方法
  1. 使用方法 : 基本与poll相同

    • 生成对象改为 epoll()
    • 将所有事件类型改为EPOLL类型
  2. epoll特点

    • epoll 效率比select poll要高
    • epoll 监控IO数量比select要多
    • epoll 的触发方式比poll要多 (EPOLLET边缘触发)

(3)协程技术

1> 基础概念
  1. 定义:纤程,微线程。是允许在不同入口点不同位置暂停或开始的计算机程序,简单来说,协程就是可以暂停执行的函数

  2. 协程原理 : 记录一个函数的上下文,协程调度切换时会将记录的上下文保存,在切换回来时进行调取,恢复原有的执行内容,以便从上一次执行位置继续执行。

  3. 协程优缺点

优点

  1. 协程完成多任务占用计算资源很少
  2. 由于协程的多任务切换在应用层完成,因此切换开销少
  3. 协程为单线程程序,无需进行共享资源同步互斥处理

缺点

协程的本质是一个单线程,无法利用计算机多核资源

python3.5以后,使用标准库asyncio和async/await 语法来编写并发代码。asyncio库通过对异步IO行为的支持完成python的协程。虽然官方说asyncio是未来的开发方向,但是由于其生态不够丰富,大量的客户端不支持awaitable需要自己去封装,所以在使用上存在缺陷。更多时候只能使用已有的异步库(asyncio等),功能有限


2> 第三方协程模块
  1. greenlet模块
  • 安装 : sudo pip3 install greenlet

  • 函数

1
2
3
4
5
6
greenlet.greenlet(func)
功能:创建协程对象
参数:协程函数

g.switch()
功能:选择要执行的协程函数
  1. gevent模块
  • 安装:sudo pip3 install gevent

  • 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
gevent.spawn(func,argv)
功能: 生成协程对象
参数:func 协程函数
argv 给协程函数传参(不定参)
返回值: 协程对象

gevent.joinall(list,[timeout])
功能: 阻塞等待协程执行完毕
参数:list 协程对象列表
timeout 超时时间

gevent.sleep(sec)
功能: gevent睡眠阻塞
参数:睡眠时间

* gevent协程只有在遇到gevent指定的阻塞行为时才会自动在协程之间进行跳转
如gevent.joinall(),gevent.sleep()带来的阻塞
  • monkey脚本

作用:在gevent协程中,协程只有遇到gevent指定类型的阻塞才能跳转到其他协程,因此,我们希望将普通的IO阻塞行为转换为可以触发gevent协程跳转的阻塞,以提高执行效率。

转换方法:gevent 提供了一个脚本程序monkey,可以修改底层解释IO阻塞的行为,将很多普通阻塞转换为gevent阻塞。

使用方法

【1】 导入monkey

    from gevent  import monkey

【2】 运行相应的脚本,例如转换socket中所有阻塞

    monkey.patch_socket()

【3】 如果将所有可转换的IO阻塞全部转换则运行all

    monkey.patch_all()

【4】 注意:脚本运行函数需要在对应模块导入前执行