学一点python基础

pythonr小基础

文章目录

简介

认识计算机和程序

  1. 程序员:

    程序设计人员。

  2. 程序:

    一组计算机能识别和执行的指令,是实现某种需求的软件。

  3. 操作系统:

    管理和控制计算机软件与硬件资源的程序;

    隔离不同硬件的差异,使开发程序简单化。

    例如,Windows,Linux,Unix。

  4. 硬件:

    主板–计算机的主要电路系统。

    CPU --主要负责执行程序指令,处理数据。

    硬盘–持久化存储数据的记忆设备,容量大,速度慢。

    内存–临时存储数据的记忆设备,容量小,速度快。

    IO设备–键盘、鼠标、显示器。

Python 定义

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

Python程序的执行

交互式

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

​ 1. 打开终端

​ 2. 进入交互式:python3

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

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

文件式

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

​ 1. 编写文件。

​ 2. 打开终端

​ 3. 进入程序所在目录:cd 目录

​ 4. 执行程序: python3 文件名

python执行过程

计算机只能识别机器码(1010),不能识别源代码(python)。

  1. 由源代码转变成机器码的过程分成两类:编译和解释。

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

    – 优点:运行速度快

    – 缺点:开发效率低,不能跨平台。

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

    – 优点:开发效率高,可以跨平台;

    – 缺点:运行速度慢。

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

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

    |————1次———|

python解释器类型

  1. CPython(C语言开发)

  2. Jython (java开发)

  3. IronPython (.net开发)

基本数据类型

注释

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

单行注释:以#号开头。

多行注释:三引号开头,三引号结尾。

函数

表示一个功能,函数定义者是提供功能的人,函数调用者是使用功能的人。

例如:

​ print(数据) 作用:将括号中的内容显示在控制台中

​ 变量 = input(“需要显示的内容”) 作用:将用户输入的内容赋值给变量

变量

定义:关联一个对象的标识符。

命名:必须是字母或下划线开头,后跟字母、数字、下划线。不能使用关键字(蓝色),否则发生语法错误:SyntaxError: invalid syntax。

建议命名:字母小写,多个单词以下划线隔开。class_name

赋值:创建一个变量或改变一个变量关联的数据。

语法:变量名 = 数据

​ 变量名1 = 变量名2 = 数据

​ 变量名1, 变量名2, = 数据1, 数据2

del 语句

语法:

​ del 变量名1, 变量名2

作用:

​ 用于删除变量,同时解除与对象的关联.如果可能则释放对象。

自动化内存管理的引用计数:

​ 每个对象记录被变量绑定(引用)的数量,当为0时被销毁。

核心数据类型

在python中变量没有类型,但关联的对象有类型。

通过type函数可查看。

空值对象 None

表示不存在的特殊对象。

作用:占位和解除与对象的关联

整形int

表示整数,包含正数、负数、0。

​ 如: -5, 100, 0

字面值:

​ 十进制:5

​ 二进制:0b开头,后跟1或者1

​ 八进制:0o开头,后跟0~7

​ 十六进制:0x开头,后跟09,AF,a~f

小整数对象池:

CPython 中整数 -5 至 256,永远存在小整数对象池中,不会被释放并可重复使用。

浮点型float

表示小数,包含正数、负数,0.0)。

字面值:

​ 小数:1.0 2.5

​ 科学计数法:e/E (正负号) 指数

​ 1.23e-2 (等同于0.0123)

​ 1.23456e5(等同于123456.0)

字符串str

定义:

由一系列字符组成的不可变序列容器,存储的是字符的编码值。用来记录文本信息(文字信息)。

编码:

\1. 字节byte:计算机最小存储单位,等于8 位bit.

\2. 字符:单个的数字,文字与符号。

\3. 字符集(码表):存储字符与二进制序列的对应关系。

\4. 编码:将字符转换为对应的二进制序列的过程。

\5. 解码:将二进制序列转换为对应的字符的过程。

\6. 编码方式:

  • –ASCII编码:包含英文、数字等字符,每个字符1个字节。
  • –GBK编码:兼容ASCII编码,包含21003个中文;英文1个字节,汉字2个字节。
  • –Unicode字符集:国际统一编码,旧字符集每个字符2字节,新字符集4字节。
  • – UTF-8编码:Unicode的存储与传输方式,英文1字节,中文3字节。
相关函数(取ASCll码):

\1. ord(字符串):返回该字符串的Unicode码。

\2. chr(整数):返回该整数对应的字符串。

字面值
  • 单引和双引号的区别:

    \1. 单引号内的双引号不算结束符

    \2. 双引号内的单引号不算结束符

  • 三引号作用:

    \1. 换行会自动转换为换行符\n

    \2. 三引号内可以包含单引号和双引号

    \3. 作为文档字符串(doc

转义字符

\1. 改变字符的原始含义。

  • \’ \” \””” \n \ \t \0 空字符

\2. 原始字符串:取消转义。

  • a = r”C:\newfile\test.py”
字符串格式化

\1. 定义:

​ 生成一定格式的字符串。

\2. 语法:

​ 字符串%(变量)

​ “我的名字是%s,年龄是%s” % (name, age)

\3. 类型码:

​ %s 字符串 %d整数 %f 浮点数

列表list

定义

由一系列变量组成的可变序列容器。

基础操作

\1. 创建列表:

​ 列表名 = []

​ 列表名 = list(可迭代对象)

\2. 添加元素:

​ 列表名.append(元素)

​ 列表.insert(索引,元素)

\3. 定位元素:

​ 索引、切片

\4. 遍历列表:

​ 正向:

​ for 变量名 in 列表名:

​ 变量名就是元素

​ 反向:

​ for 索引名 in range(len(列表名)-1,-1,-1):

​ 列表名[索引名]就是元素

\5. 删除元素:

​ 列表名.remove(元素)

​ del 列表名[索引或切片]

列表推导式

\1. 定义:

​ 使用简易方法,将可迭代对象转换为列表。

\2. 语法:

​ 变量 = [表达式 for 变量 in 可迭代对象]

​ 变量 = [表达式 for 变量 in 可迭代对象 if 条件]

\3. 说明:

​ 如果if真值表达式的布尔值为False,则可迭代对象生成的数据将被丢弃。

列表推导式嵌套

\1. 语法:

​ 变量 = [表达式 for 变量1 in 可迭代对象1 for 变量2 in可迭代对象2]

\2. 传统写法:

​ result = []

​ for r in [“a”, “b”, “c”]:

​ for c in [“A”, “B”, “C”]:

​ result.append(r + c)

\3. 推导式写法:

​ result = [r + c for r in list01 for c in list02]

复数complex

由实部和虚部组成的数字。

虚部是以j或J结尾。

字面值: 1j 1+1j 1-1j

集合 set

定义

\1. 由一系列不重复的不可变类型变量组成的可变散列容器。

\2. 相当于只有键没有值的字典(键则是集合的数据)。

基础操作

\1. 创建空集合:

​ 集合名 = set()

​ 集合名 = set(可迭代对象)

\2. 创建具有默认值集合:

​ 集合名 = {1, 2, 3}

​ 集合名 = set(可迭代对象)

\3. 添加元素:

​ 集合名.add(元素)

\4. 删除元素:

​ 集合名.discard(元素)

运算

\1. 交集&:返回共同元素。

  • s1 = {1, 2, 3}
  • s2 = {2, 3, 4}
  • s3 = s1 & s2 # {2, 3}

\2. 并集:返回不重复元素

  • s1 = {1, 2, 3}
  • s2 = {2, 3, 4}
  • s3 = s1 | s2 # {1, 2, 3, 4}

\3. 补集-:返回只属于其中之一的元素

  • s1 = {1, 2, 3}
  • s2 = {2, 3, 4}
  • s1 - s2 # {1} 属于s1但不属于s2

补集^:返回不同的的元素

  • s1 = {1, 2, 3}
  • s2 = {2, 3, 4}
  • s3 = s1 ^ s2 # {1, 4} 等同于(s1-s2 | s2-s1)

\4. 子集<:

  • 判断一个集合的所有元素是否完全在另一个集合中

\5. 超集>:

  • ​ 判断一个集合是否具有另一个集合的所有元素
  • s1 = {1, 2, 3}
  • s2 = {2, 3}
  • ​ s2 < s1 # True
  • s1 > s2 # True

\6. 相同或不同== !=:判断集合中的所有元素是否和另一个集合相同。

  • s1 = {1, 2, 3}
  • s2 = {3, 2, 1}
  • s1 == s2 # True
  • s1 != s2 # False
  • 子集或相同,超集或相同 <= >=
集合推导式

\1. 定义:

  • 使用简易方法,将可迭代对象转换为集合。

\2. 语法:

  • {表达式 for 变量 in 可迭代对象}
  • {表达式 for 变量 in 可迭代对象 if 条件}
固定集合 frozenset
定义
  • 不可变的集合。
作用
  • 固定集合可以作为字典的键,还可以作为集合的值。
基础操作
  • 创建固定集合:frozenset(可迭代对象)
运算
  • 等同于set

字典 dict

定义

\1. 由一系列键值对组成的可变散列容器。

\2. 散列:对键进行哈希运算,确定在内存中的存储位置,每条数据存储无先后顺序。

\3. 键必须惟一且不可变(字符串/数字/元组),值没有限制。

基础操作

\1. 创建字典:

​ 字典名 = {键1:值1,键2:值2}

​ 字典名 = dict (可迭代对象)

\2. 添加/修改元素:

​ 语法:

​ 字典名[键] = 数据

​ 说明:

​ 键不存在,创建记录。

​ 键存在,修改映射关系。

\3. 获取元素:

​ 变量 = 字典名[键] # 没有键则错误

\4. 遍历字典:

​ for 键名 in 字典名:

​ 字典名[键名]

​ for 键名,值名 in 字典名.items():

​ 语句

\5. 删除元素:

​ del 字典名[键]

字典推导式

\1. 定义:

​ 使用简易方法,将可迭代对象转换为字典。

\2. 语法:

​ {键:值 for 变量 in 可迭代对象}

​ {键:值 for 变量 in 可迭代对象 if 条件}

元组 tuple

定义

由一系列变量组成的不可变序列容器。 不可变是指一但创建,不可以再添加/删除/修改元素。

基础操作

\1. 创建空元组:

​ 元组名 = ()

​ 元组名 = tuple()

\2. 创建非空元组:

​ 元组名 = (20,)

​ 元组名 = (1, 2, 3)

​ 元组名 = 100,200,300

​ 元组名 = tuple(可迭代对象)

\3. 获取元素:

​ 索引、切片

\4. 遍历元组:

​ 正向:

​ for 变量名 in 列表名:

​ 变量名就是元素

​ 反向:

​ for 索引名 in range(len(列表名)-1,-1,-1):

​ 元祖名[索引名]就是元素

作用

\1. 元组与列表都可以存储一系列变量,由于列表会预留内存空间,所以可以增加元素。

\2. 元组会按需分配内存,所以如果变量数量固定,建议使用元组,因为占用空间更小。

\3. 应用:

​ 变量交换的本质就是创建元组:x, y = y, x

​ 格式化字符串的本质就是创建元祖:“姓名:%s, 年龄:%d” % (“tarena”, 15)

布尔bool

用来表示真和假的类型

True 表示真(条件满足或成立),本质是1

False 表示假(条件不满足或不成立),本质是0

数据类型转换
  • 转换为整形: int(数据)

  • 转换为浮点型:float(数据)

  • 转换为字符串:str(数据)

  • 转换为布尔:bool(数据)

​ 结果为False:bool(0) bool(0.0) bool(None)

  • 混合类型自动升级:

​ 1 + 2.14 返回的结果是 3.14

​ 1 + 3.0 返回结果是: 4.0

基本运算符

算术运算符
  • 加法
  • 减法
  • 乘法

/ 除法:结果为浮点数

// 地板除:除的结果去掉小数部分

% 求余

** 幂运算

优先级从高到低: ()

​ **

​ * / % //

​ + -

增强运算符

​ 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

比较运算符

< 小于

<= 小于等于

> 大于

>= 大于等于

== 等于

!= 不等于

返回布尔类型的值

比较运算的数学表示方式:0 <= x <= 100

逻辑运算符

与 and

表示并且的关系,一假俱假。

示例:

True and True # True

True and False # False

False and True # False

False and False # False

或 or

表示或者的关系,一真俱真

示例:

True or True # True

True or False # True

False or True # True

False or False # False

非 not

表示取反

例如:

not True # 返回False

not False # 返回True

短路运算

一但结果确定,后面的语句将不再执行。

身份运算符

语法:

	x is y

	x is not y

作用:

  • is 用于判断两个对象是否是同一个对象,是时返回True,否则返回False。
  • is not 的作用与is相反
运算符优先级

高到低:

  • 算数运算符
  • 比较运算符
  • 快捷运算符
  • 身份运算符
  • 逻辑运算符

基本语句

\1. 物理行:程序员编写代码的行。

\2. 逻辑行:python解释器需要执行的指令。

\3. 建议一个逻辑行在一个物理行上。

\4. 如果一个物理行中使用多个逻辑行,需要使用分号;隔开。

\5. 如果逻辑行过长,可以使用隐式换行或显式换行。

​ 隐式换行:所有括号的内容换行,称为隐式换行

​ 括号包括: () [] {} 三种

​ 显式换行:通过折行符 \ (反斜杠)换行,必须放在一行的末尾,目的是告诉解释器,下一行也是 本行的语句。

pass 语句

通常用来填充语法空白。

选择语句

If elif else 语句

条件表达式(三目运算符)
  • 语法:变量 = 结果1 if 条件 else 结果2
  • 作用:根据条件(True/False) 来决定返回结果1还是结果2。
循环语句

while

for 语句(迭代)

for in

range 函数
  • \1. 作用:
    • 用来创建一个生成一系列整数的可迭代对象(也叫整数序列生成器)。
  • \2. 语法:
    • range(开始点,结束点,间隔)
  • \3. 说明:
    • 函数返回的可迭代对象可以用for取出其中的元素
    • 返回的数字不包含结束点
    • 开始点默认为0
    • 间隔默认值为1
跳转语句
break 语句
  • \1. 跳出循环体,后面的代码不再执行。
  • \2. 可以让while语句的else部分不执行。
continue 语句
  • 跳过本次,继续下次循环。

容器通用操作

数学运算符

\1. +:用于拼接两个容器

\2. +=:用原容器与右侧容器拼接,并重新绑定变量

\3. *:重复生成容器元素

\4. *=:用原容器生成重复元素, 并重新绑定变量

\5. < <= > >= == !=:依次比较两个容器中元素,一但不同则返回比较结果。

成员运算符

\1. 语法:

  • 数据 in 序列
  • 数据 not in 序列

\2. 作用:

  • 如果在指定的序列中找到值,返回bool类型。
索引index

\1. 作用:访问容器元素

\2. 语法:容器[整数]

\3. 说明:

  • 正向索引从0开始,第二个索引为1,最后一个为len(s)-1。
  • 反向索引从-1开始,-1代表最后一个,-2代表倒数第二个,以此类推,第一个是-len(s)。
切片slice

\1. 作用:

  • 从容器中取出相应的元素重新组成一个容器。

\2. 语法:

  • 容器[(开始索引):(结束索引)(:(步长))]

\3. 说明:

  • 小括号()括起的部分代表可省略
  • 结束索引不包含该位置元素
  • 步长是切片每次获取完当前元素后移动的偏移量
内建函数

\1. len(x) 返回序列的长度

\2. max(x) 返回序列的最大值元素

\3. min(x) 返回序列的最小值元素

\4. sum(x) 返回序列中所有元素的和(元素必须是数值类型)

函数 function

定义
  • \1. 用于封装一个特定的功能,表示一个功能或者行为。

  • \2. 函数是可以重复执行的语句块, 可以重复调用。

作用
  • 提高代码的可重用性和可维护性(代码层次结构更清晰)。
定义函数

\1. 语法:

​ def 函数名(形式参数):

函数体

\2. 说明:

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

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

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

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

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

调用函数

\1. 语法:函数名(实际参数)

\2. 说明:根据形参传递内容。

返回值

\1. 定义:

​ 方法定义者告诉调用者的结果。

\2. 语法:

​ return 数据

\3. 说明:

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

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

可变/不可变类型在传参时的区别

\1. 不可变类型参数有:

  • 数值型(整数,浮点数,复数)

  • 布尔值bool

  • None 空值

  • 字符串str

  • 元组tuple

  • 固定集合frozenset

\2. 可变类型参数有:

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

\3. 传参说明:

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

\1. 作用域:变量起作用的范围。

\2. Local局部作用域:函数内部。

\3. Enclosing 外部嵌套作用域 :函数嵌套。

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

\5. Builtin内置模块作用域:builtins.py文件。

变量名的查找规则

\1. 由内到外:L -> E -> G -> B

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

局部变量

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

\2. 只能在函数内部使用

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

全局变量

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

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

global 语句

\1. 作用:

​ 在函数内部修改全局变量。

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

\2. 语法:

​ global 变量1, 变量2, …

\3. 说明

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

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

nonlocal 语句

\1. 作用:

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

\2. 语法

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

\3. 说明

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

函数参数
实参传递方式argument
  • 位置传参
    • 定义:实参与形参的位置依次对应。
  • 序列传参
    • 定义:实参用*将序列拆解后与形参的位置依次对应。
  • 关键字传参
    • 定义:实参根据形参的名字进行对应。
  • 字典关键字传参
    • \1. 定义:实参用**将字典拆解后与形参的名字进行对应。
    • \2. 作用:配合形参的缺省参数,可以使调用者随意传参。
形参定义方式 parameter
  • 缺省参数

    • \1. 语法:
      • def 函数名(形参名1=默认实参1, 形参名2=默认实参2, …):
      • 函数体
    • \2. 说明:
      • 缺省参数必须自右至左依次存在,如果一个参数有缺省参数,则其右侧的所有参数都必须有缺省参数。
      • 缺省参数可以有0个或多个,甚至全部都有缺省参数。
  • 位置形参

    • 语法:
      • def 函数名(形参名1, 形参名2, …):
      • 函数体
  • 星号元组形参

    • \1. 语法:
      • def 函数名(*元组形参名):
      • 函数体
    • \2. 作用:
      • 收集多余的位置传参。
    • \3. 说明:
      • 一般命名为’args’
      • 形参列表中最多只能有一个
  • 命名关键字形参

    • \1. 语法:

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

        ​ 函数体

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

    • \2. 作用:

      • 强制实参使用关键字传参
  • 双星号字典形参

    • \1. 语法:
      • def 函数名(**字典形参名):
      • 函数体
    • \2. 作用:
      • 收集多余的关键字传参
    • \3. 说明:
      • 一般命名为’kwargs’
      • 形参列表中最多只能有一个

基础补充

深拷贝和浅拷贝

浅拷贝:

复制过程中,只复制一层变量,不会复制深层变量绑定的对象的复制过程。

对于浅拷贝,字典、列表、元组等类型,它们只拷贝第一层地址

在这里插入图片描述

深拷贝:

复制整个依懒的变量。

在这里插入图片描述

对于数字和字符串的赋值、浅拷贝、深拷贝在内存当中用的都是同一块地址

在这里插入图片描述

列表VS字符串

\1. 列表和字符串都是序列,元素之间有先后顺序关系。

\2. 字符串是不可变的序列,列表是可变的序列。

\3. 字符串中每个元素只能存储字符,而列表可以存储任意类型。

\4. 列表和字符串都是可迭代对象。

\5. 函数:

​ 将多个字符串拼接为一个。

​ result = “连接符”.join(列表)

​ 将一个字符串拆分为多个。

​ 列表 = “a-b-c-d”.split(“分隔符”)

字典 VS 列表

\1. 都是可变容器。

\2. 获取元素方式不同,列表用索引,字典用键。

\3. 字典的插入,删除,修改的速度快于列表。

\4. 列表的存储是有序的,字典的存储是无序的。

面对对象编程

概述

面向过程

\1. 分析出解决问题的步骤,然后逐步实现。

例如:婚礼筹办

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

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

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

\2. 公式:程序 = 算法 + 数据结构

\3. 优点:所有环节、细节自己掌控。

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

面向对象

\1. 找出解决问题的人,然后分配职责。

例如:婚礼筹办

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

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

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

\2. 公式:程序 = 对象 + 交互

\3. 优点

(1) 思想层面:

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

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

(2) 技术层面:

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

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

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

**\4. 缺点:**学习曲线陡峭。

类和对象

\1. 类:一个抽象的概念,即生活中的”类别”。

\2. 对象:类的具体实例,即归属于某个类别的”个体”。

\3. 类是创建对象的”模板”。

– 数据成员:名词类型的状态。

– 方法成员:动词类型的行为。

\4. 类与类行为不同,对象与对象数据不同。

语法

定义类

\1. 代码

class 类名:

​	“””文档说明”””

​	def _init_(self,参数列表):

​		self.实例变量 = 参数

方法成员

\2. 说明

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

创建对象(实例化)

​ 变量 = 构造函数 (参数列表)

实例成员

实例变量

  • \1. 语法

    (1) 定义:对象.变量名

    (2) 调用:对象.变量名

  • \2. 说明

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

    w01 = Wife()

    w01.name = “丽丽”

    w01.name = “莉莉”

  • (2) 通常在构造函数(__ init __)中创建。

    w01 = Wife(“丽丽”,24)

    print(w01.name)

    (3) 每个对象存储一份,通过对象地址访问。

  • \3. 作用:描述某个对象的数据。

  • \4. __ dict __:对象的属性,用于存储自身实例变量的字典。

实例方法

\1. 语法

​ (1) 定义: def 方法名称(self, 参数列表):

​ 方法体

​ (2) 调用: 对象地址.实例方法名(参数列表)

​ 不建议通过类名访问实例方法

\2. 说明

​ (1) 至少有一个形参,第一个参数绑定调用这个方法的对象,一般命名为"self"。

​ (2) 无论创建多少对象,方法只有一份,并且被所有对象共享。

\3. 作用:表示对象行为。

类成员

类变量

\1. 语法

​ (1) 定义:在类中,方法外定义变量。

​ class 类名:

​ 变量名 = 表达式

(2) 调用:类名.变量名

​ 不建议通过对象访问类变量

\2. 说明

​ (1) 存储在类中。

​ (2) 只有一份,被所有对象共享。

\3. 作用:描述所有对象的共有数据。

类方法

\1. 语法

(1) 定义:

  @classmethod

  def 方法名称(cls,参数列表):

​     方法体

(2) 调用:类名.方法名(参数列表)

	不建议通过对象访问类方法

\2. 说明

​ (1) 至少有一个形参,第一个形参用于绑定类,一般命名为’cls’

​ (2) 使用@classmethod修饰的目的是调用类方法时可以隐式传递类。

​ (3) 类方法中不能访问实例成员,实例方法中可以访问类成员。

\3. 作用:操作类变量。

静态方法

#创建model02.py
#定义一个函数 my_fun()
def my_fun():
    print('my_fun')

class MyClass02:
    def __init__(self, a):
        self.a = a

    def fun02(self):
        print("fun02")

    @classmethod
    def fun03(cls):
        print("fun03")

\1. 语法

(1) 定义:

  @staticmethod

  def 方法名称(参数列表):

​      方法体

(2) 调用:类名.方法名(参数列表)

	不建议通过对象访问静态方法

\2. 说明

​ (1) 使用@ staticmethod修饰的目的是该方法不需要隐式传参数。

​ (2) 静态方法不能访问实例成员和类成员

\3. 作用:定义常用的工具函数。

三大特征

封装

数据角度讲

\1. 定义:

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

\2. 优势:

​ 将数据与对数据的操作相关联。

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

行为角度讲

\1. 定义:

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

\2. 优势:

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

\3. 私有成员:

​ (1) 作用:无需向类外提供的成员,可以通过私有化进行屏蔽。

​ (2) 做法:命名使用双下划线开头。

​ (3) 本质:障眼法,实际也可以访问。

​ 私有成员的名称被修改为:__ 类名 __ 成员名,可以通过 __ dict __ 属性或dir函数查看。

\4. 属性@property:

​ 公开的实例变量,缺少逻辑验证。私有的实例变量与两个公开的方法相结合,又使调用者的操作略显复杂。而属性可以将两个方法的使用方式像操作变量一样方便。

(1) 定义:

@property

def 属性名(self):return self.__属性名

@属性名.setter

def 属性名(self, value):

​	self.__属性名= value

(2) 调用:

​ 对象.属性名 = 数据

​ 变量 = 对象.属性名

(3) 说明:

​ 通常两个公开的属性,保护一个私有的变量。

​ @property 负责读取,@属性名.setter 负责写入

​ 只写:属性名= property(None, 写入方法名)

设计角度讲

\1. 定义:

(1) 分而治之

​ 将一个大的需求分解为许多类,每个类处理一个独立的功能。

(2) 变则疏之

​ 变化的地方独立封装,避免影响其他类。

(3) 高 内 聚

​ 类中各个方法都在完成一项任务(单一职责的类)。

(4) 低 耦 合

​ 类与类的关联性与依赖度要低(每个类独立),让一个类的改变,尽少影响其他类。

\2. 优势:

​ 便于分工,便于复用,可扩展性强。

案例:信息管理系统

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Sw4IZW0n-1591240434734)(file:///C:\Users\Administrator\AppData\Local\Temp\ksohtml4752\wps1.jpg)]

简易信息管理系统带io

#student_manager_system

class StudentModel:
    '''
    学生模型
    '''
    def __init__(self, name="", age=0, score=0,id=0):
        '''
        创建学生对象
        :param id: 编号 该学生的唯一标识
        :param name: 姓名 str
        :param age: 年龄 int
        :param score: 成绩 int
        '''
        self.id = id
        self.name = name
        self.age = age
        self.score = score

class StudentManagerController:
    '''
    学生管理控制器 处理业务逻辑
    '''
    __stu_id = 1000
    def __init__(self):
        self.__stu_list = []

    @property
    def stu_list(self):
        return self.__stu_list

    def add_student(self,stu):
        #为学生设置id 递增
        # StudentManagerController.__stu_id += 1
        #stu.id = StudentManagerController.__stu_id
        stu.id = self.__generate_id()
        #将学生添加到学生列表
        self.__stu_list.append(stu)

    def __generate_id(self):
        StudentManagerController.__stu_id += 1
        return StudentManagerController.__stu_id


    # 删除学生remove_student
        #根据id删除学生
        #删除后返回结果  成功True/失败False

    def remove_student(self,id):
        '''
        根据编号删除学生
        :param id: 编号
        :return: 删除的结果
        '''
        for item in self.__stu_list:
            if item.id == id:
                self.__stu_list.remove(item)
                return True
        return False

    def update_student(self,stu):
        for item in self.__stu_list:
            if item.id == stu.id:
                item.name = stu.name
                item.age = stu.age
                item.score = stu.score
                return True
        return False



    #了解清楚学生管理系统的运行流程
    #根据成绩排序
    #考虑如何重构shopping.py(提高)


    # 根据成绩排序order_by_score。
    #你是谁  你要找谁
    def order_by_score(self):
        for i in range(len(self.__stu_list)-1):
            for c in range(i+1,len(self.__stu_list)):
                if self.__stu_list[i].score > self.__stu_list[c].score:
                    self.__stu_list[i],self.__stu_list[c] = self.__stu_list[c],self.__stu_list[i]

class StudentManagerView:
    def __init__(self):
        self.__manager = StudentManagerController()

    def __display_menu(self):
        print('+---------------------+')
        print('| 1)添加学生信息        |')
        print('| 2)显示学生信息        |')
        print('| 3)删除学生信息        |')
        print('| 4)修改学生信息        |')
        print('| 5)按照成绩升序排序     |')
        print('+---------------------+')

    def __select_menu(self):
        option = input('请输入:')
        if option == '1':
            self.__input_students()
        elif option == '2':
            self.__output_students(self.__manager.stu_list)
        elif option == '3':
            self.__delete_student()
        elif option == '4':
            self.__modify_student()
        elif option == '5':
            self.__output_student_by_socre()

    def main(self):
        '''
        界面入口
        :return:
        '''
        while True:
            self.__display_menu()
            self.__select_menu()

    # 输入学生__input_students
    def __input_students(self):
        #收集学生信息
        #要求输入 姓名 年龄 成绩
        #创建学生对象(姓名 年龄 成绩)
        #去控制器找add_student方法
        name = input('请输入学生姓名:')
        age = int(input('请输入学生年龄:'))
        score = int(input('请输入学生成绩:'))
        stu = StudentModel(name,age,score)
        self.__manager.add_student(stu)

    # 输出学生__output_students
    def __output_students(self,list):
        for item in list:
            print(item.name,item.age,item.score,item.id)

    # 删除学生__delete_student
    def __delete_student(self):
        #需要用户输入学生id
        #调用管理器对象的删除学生方法
        #如果结果为True 显示删除成功
        #否则显示删除失败
        id = int(input('请输入要删除学生的编号:'))
        if self.__manager.remove_student(id):
            print('删除成功')
        else:
            print('删除失败')

    #修改学生信息__modify_student
    def __modify_student(self):
        #收集用户输入的信息保存到对象
        #调用管理器的修改学生的方法
        id = int(input('请输入要修改学生的编号:'))
        name = input('请输入新的学生姓名:')
        age = int(input('请输入新的学生年龄:'))
        score = int(input('请输入新的学生成绩:'))
        stu = StudentModel(name,age,score,id)
        if self.__manager.update_student(stu):
            print('修改成功')
        else:
            print('修改失败')

    def __output_student_by_socre(self):
        self.__manager.order_by_score()
        self.__output_students(self.__manager.stu_list)

view = StudentManagerView()
# view.display_menu()
view.main()
需求

​ 实现对学生信息的增加、删除、修改和查询。

分析

​ 界面可能使用控制台,也可能使用Web等等。

\1. 识别对象:界面视图类 逻辑控制类 数据模型类

\2. 分配职责(MVC):

​ 界面视图类:负责处理界面逻辑,比如显示菜单,获取输入,显示结果等。

​ 逻辑控制类:负责存储学生信息,处理业务逻辑。比如添加、删除等

​ 数据模型类:定义需要处理的数据类型。比如学生信息。

\3. 建立交互:

​ 界面视图对象 <----> 数据模型对象 <----> 逻辑控制对象

设计

数据模型类:StudentModel

  • 数据:编号 id,姓名 name,年龄 age,成绩 score

逻辑控制类:StudentManagerController

  • 数据:学生列表 __stu_list

  • 行为:获取列表 stu_list,添加学生 add_student,删除学生remove_student,修改学生update_student,根据成绩排序order_by_score。

界面视图类:StudentManagerView

  • 数据:逻辑控制对象__manager

  • 行为:

    • 显示菜单__ display_menu,
    • 选择菜单项__select_menu_item,
    • 入口逻辑main,
    • 输入学生__ input_students,
    • 输出学生__ output_students,
    • 删除学生__ delete_student,
    • 修改学生信息__modify_student

继承

语法角度讲
继承方法

\1. 代码:

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

​		  方法体

class 子类(父类):

​		def 子类方法(self):

​			方法体

儿子 = 子类()

儿子.子类方法()

儿子.父类方法()

\2. 说明:

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

内置函数
  • isinstance(对象, 类型)

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

  • issubclass(类型,类型)

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

继承数据

\1. 代码

class 子类(父类):

 def __ init __(self,参数列表):super().__ init __(参数列表)

​		self.自身实例变量 = 参数

\2. 说明

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

定义

重用现有类的功能,并在此基础上进行扩展。

说明:子类直接具有父类的成员(共性),还可以扩展新功能。

优点

一种代码复用的方式。

缺点

耦合度高:父类的变化,直接影响子类。

设计角度讲
定义

将相关类的共性进行抽象,统一概念,隔离变化。

适用性

多个类在概念上是一致的,且需要进行统一的处理。

相关概念

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

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

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

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

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

多继承

一个子类继承两个或两个以上的基类,父类中的属性和方法同时被子类继承下来。

同名方法的解析顺序(MRO, Method Resolution Order):

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

 A

/ \

/ \

B C

\ /

\ /

​ D

多态

设计角度讲
定义

父类的同一种动作或者行为,在不同的子类上有不同的实现。

作用

\1. 在继承的基础上,体现类型的个性化(一个行为有不同的实现)。

\2. 增强程序扩展性,体现开闭原则。

语法角度讲
重写

子类实现了父类中相同的方法(方法名、参数)。

在调用该方法时,实际执行的是子类的方法。

快捷键

Ctrl + O

内置可重写函数

Python中,以双下划线开头、双下划线结尾的是系统定义的成员。我们可以在自定义类中进行重写,从而改变其行为。

转换字符串

__ str __函数:将对象转换为字符串(对人友好的)

__ repr __函数:将对象转换为字符串(解释器可识别的)

运算符重载

定义:让自定义的类生成的对象(实例)能够使用运算符进行操作。

算数运算符

在这里插入图片描述

反向算数运算符重载

在这里插入图片描述

复合运算符重载

在这里插入图片描述

比较运算重载

在这里插入图片描述

设计原则

开-闭原则(目标、总的指导思想)

Open Closed Principle

对扩展开放,对修改关闭。

增加新功能,不改变原有代码。

类的单一职责(一个类的定义)

Single Responsibility Principle

一个类有且只有一个改变它的原因。

依赖倒置(依赖抽象)

Dependency Inversion Principle

客户端代码(调用的类)尽量依赖(使用)抽象。

抽象不应该依赖细节,细节应该依赖抽象。

组合复用原则(复用的最佳实践)

Composite Reuse Principle

如果仅仅为了代码复用优先选择组合复用,而非继承复用。

组合的耦合性相对继承低。

里氏替换(继承后的重写,指导继承的设计)

Liskov Substitution Principle

父类出现的地方可以被子类替换,在替换后依然保持原功能。

子类要拥有父类的所有功能。

子类在重写父类方法时,尽量选择扩展重写,防止改变了功能。

迪米特法则(类与类交互的原则)

Law of Demeter

不要和陌生人说话。

类与类交互时,在满足功能要求的基础上,传递的数据量越少越好。因为这样可能降低耦合度。

高级基础技术

模块 Module

定义

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

作用

让一些相关的数据,函数,类有逻辑的组织在一起,使逻辑结构更加清晰。

有利于多人合作开发。

导入

import

\1. 语法:

import 模块名

import 模块名 as 别名

\2. 作用:

将某模块整体导入到当前模块中

\3. 使用:

模块名.成员

from import

\1. 语法:

from 模块名 import 成员名[ as 别名1]

作用:

将模块内的一个或多个成员导入到当前模块的作用域中。

from import *

\1. 语法:

from 模块名 import *

\2. 作用:

将某模块的所有成员导入到当前模块。

\3. 模块中以下划线(_)开头的属性,不会被导入,通常称这些成员为隐藏成员。

模块变量

__ all __变量:定义可导出成员,仅对from xx import *语句有效。

__ doc __变量:文档字符串。

__ file __变量:模块对应的文件路径名。

__ name __变量:模块自身名字,可以判断是否为主模块。

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

加载过程

在模块导入时,模块的所有语句会执行。

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

分类

\1. 内置模块(builtins),在解析器的内部可以直接使用。

\2. 标准库模块,安装Python时已安装且可直接使用。

\3. 第三方模块(通常为开源),需要自己安装。

\4. 用户自己编写的模块(可以作为其他人的第三方模块)

搜索顺序

搜索内建模块(builtins)

sys.path 提供的路径,通常第一个是程序运行时的路径。

包package

定义

将模块以文件夹的形式进行分组管理。

作用

让一些相关的模块组织在一起,使逻辑结构更加清晰。

导入

import 包名 [as 包别名] 需要设置__ all __

import 包名.模块名 [as 模块新名]

import 包名.子包名.模块名 [as 模块新名]

from 包名 import 模块名 [as 模块新名]

from 包名.子包名 import 模块名 [as 模块新名]

from 包名.子包名.模块名 import 成员名 [as 属性新名]

# 导入包内的所有子包和模块

from 包名 import *

from 包名.模块名 import *

搜索顺序

sys.path 提供的路径

__ init __.py 文件

是包内必须存在的文件

会在包加载时被自动调用

__ all __

记录from 包 import * 语句需要导入的模块

#规定其他模块from model03 import *
# 可以导入的内容
# __all__ = ['fun01']

def fun01():
    print('in model03')

#隐藏成员 使用单下划线命名
#在其他模块不能使用
#仅限 from xx import *
def _fun02():
    print('fun02')

案例:

my_ project /

main.py     

common/

  __ init __.py

​	double_list_helper.py

​	list_helper.py

  skill_system/

​    __ init __.py

​    skill_deployer.py

​		skill_manager.py

异常处理Error

异常

\1. 定义:运行时检测到的错误。

\2. 现象:当异常发生时,程序不会再向下执行,而转到函数的调用语句。

\3. 常见异常类型

– 名称异常(NameError):变量未定义。

– 类型异常(TypeError):不同类型数据进行运算。

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

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

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

– 为实现异常(NotImplementedError):尚未实现的方法。

– 异常基类Exception。

处理

\1. 语法:

try:

  可能触发异常的语句

except 错误类型1 [as 变量1]:

  处理语句1

except 错误类型2 [as 变量2]:

  处理语句2

except Exception  [as 变量3]:

  不是以上错误类型的处理语句

else:

  未发生异常的语句

finally:

无论是否发生异常的语句

\2. 作用:将程序由异常状态转为正常流程。

\3. 说明:

as 子句是用于绑定错误对象的变量,可以省略

except子句可以有一个或多个,用来捕获某种类型的错误。

else子句最多只能有一个。

finally子句最多只能有一个,如果没有except子句,必须存在。

如果异常没有被捕获到,会向上层(调用处)继续传递,直到程序终止运行。

raise 语句

\1. 作用:抛出一个错误,让程序进入异常状态。

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

自定义异常

\1. 定义:

class 类名Error(Exception):def __init__(self,参数):super().__init__(参数)

​			self.数据 = 参数

\2. 调用:

try:.

raise 自定义异常类名(参数).except 定义异常类 as 变量名:

​			变量名.数据

\3. 作用:封装错误信息

迭代

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

#面试题:能够参与for循环的对象 必须具备什么样的条件?
#答:对象必须具有__iter__()方法

list01 = [10,20,15,76,28]
# for item in list01:
#     print(item)

#原理:
#通过__iter__获取迭代器对象(10,20,15,76,28)
iterator = list01.__iter__()
while True:
    #如果迭代器中没有可以继续__next__的值
    #会抛出"停止迭代" 异常
    try:
        item = iterator.__next__()#StopIteration
        print(item)
    except StopIteration:
        break

for item in iterator:
    print(item)
#一个迭代器只能使用一次
for item in iterator:
    print(item)

可迭代对象iterable

\1. 定义:具有__ iter __函数的对象,可以返回迭代器对象。

\2. 语法

-- 创建:

class 可迭代对象名称:

 def __iter__(self):

   return 迭代器

-- 使用:

for 变量名 in 可迭代对象:

语句

\3. 原理:

迭代器 = 可迭代对象.__iter__()

while True:

  try:print(迭代器.__next__())

  except StopIteration:break

迭代器对象 iterator

\1. 定义:可以被next()函数调用并返回下一个值的对象。

\2. 语法

class 迭代器类名:

  def __init__(self, 聚合对象):

​    self.聚合对象= 聚合对象 

 

  def __next__(self):if 没有元素:raise StopIteration

​      return 聚合对象元素

\3. 说明:

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

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

生成器generator

\1. 定义:

能够动态(循环一次计算一次返回一次)提供数据的可迭代对象。

\2. 作用:

在循环过程中,按照某种算法推算数据,不必创建容器存储完整的结果,从而节省内存空间。数据量越大,优势越明显。

\3. 以上作用也称之为延迟操作或惰性操作,通俗的讲就是在需要的时候才计算结果,而不是一次构建出所有结果。

生成器函数

\1. 定义:含有yield语句的函数,返回值为生成器对象。

# class MyRange:
#     def __init__(self,stop_value):
#         self.__stop_value= stop_value
#
#     def __iter__(self):
#         number = -1
#         while number <  self.__stop_value -1 :
#             number +=1
#             yield number
#
#
# for item in MyRange(7):
#     print(item)

def my_range(stop):
    number = -1
    while number <  stop -1 :
        number +=1
        yield number

# 惰性操作/延迟操作
# 返回值是生成器对象(可迭代对象 + 迭代器对象)
iterator = my_range(10)
for item in iterator:
    print(item)

\2. 语法

-- 创建:

def 函数名():yield 数据

…

​	-- 调用:

​	 for 变量名 in 函数名():

​		 语句

\3. 说明:

– 调用生成器函数将返回一个生成器对象,不执行函数体。

– yield翻译为”产生”或”生成”

\4. 执行过程:

(1) 调用生成器函数会自动创建迭代器对象。

(2) 调用迭代器对象的__ next __()方法时才执行生成器函数。

(3) 每次执行到yield语句时返回数据,暂时离开。

(4) 待下次调用__ next __()方法时继续从离开处继续执行。

\5. 原理:生成迭代器对象的大致规则如下

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

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

内置生成器

枚举函数 enumerate

\1. 语法:

for 变量 in enumerate(可迭代对象):

   语句


for 索引, 元素in enumerate(可迭代对象):

  语句

\2. 作用:遍历可迭代对象时,可以将索引与元素组合为一个元组。

zip
\1. 语法:

for item in zip(可迭代对象1, 可迭代对象2.):

  		语句

\2. 作用:将多个可迭代对象中对应的元素组合成一个个元组,生成的元组个数由最小的可迭代对象决定。

生成器表达式

\1. 定义:用推导式形式创建生成器对象。

\2. 语法:变量 = ( 表达式 for 变量 in 可迭代对象 [if 真值表达式] )

"""
    生成器表达式
    练习:exercise09.py
"""

list01  = [34,4,"a","b",1.5,1.8,True,False]

# 生成器函数:为其他人提供功能
def find01():
    for item in list01:
        if type(item) == str:
            yield item

re = find01()
for item in re:
    print(item)

# 生成器表达式:为自己提供功能
re = (item for item in list01 if type(item) == str)
print(*re)
# for item in re:
#     print(re)

函数式编程

\1. 定义:用一系列函数解决问题。

– 函数可以赋值给变量,赋值后变量绑定函数。

– 允许将函数作为参数传入另一个函数。

– 允许函数返回一个函数。

\2. 高阶函数:将函数作为参数或返回值的函数。

函数作为参数

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

lambda 表达式(匿名函数)

\1. 定义:是一种匿名方法。

"""
    lambda : 匿名函数
    作用:充当实参

"""
def fun01():
    print("fun01")

fun01()

# 无参数 无返回值
a = lambda :print("fun01")
a()

def fun02(func):
    print("fun02")
    func()

# 将函数作为参数,建议使用lambda.
fun02(lambda :print("fun01"))

def fun03(a,b,c):
    print("fun03")

# 有参数lambda
b = lambda a,b,c:print("fun03")

def fun04():
    print("fun04")
    print("fun04又执行喽")

# lambda 函数体只能有一句话
# fun02(lambda :print("fun04++");print("fun04又执行喽"))
# 不支持赋值语句
# fun02(lambda a:a.name = "zs")

\2. 作用:

作为参数传递时语法简洁,优雅,代码可读性强。

随时创建和销毁,减少程序耦合度。

\3. 语法

-- 定义:

变量 = lambda 形参: 方法体

​	  -- 调用:

​			变量(实参)

\4. 说明:

– 形参没有可以不填

– 方法体只能有一条语句,且不支持赋值语句。

内置高阶函数

\1. map(函数,可迭代对象):

使用可迭代对象中的每个元素调用函数,将返回值作为新可迭代对象元素;返回值为新可迭代对象。

\2. filter(函数,可迭代对象):

根据条件筛选可迭代对象中的元素,返回值为新可迭代对象。

\3. sorted(可迭代对象,key = 函数,reverse = bool值):

排序,返回值为排序结果。

\4. max(可迭代对象,key = 函数):

根据函数获取可迭代对象的最大值。

\5. min(可迭代对象,key = 函数):

根据函数获取可迭代对象的最小值。

函数作为返回值

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

闭包

\1. 三要素:

– 必须有一个内嵌函数。

– 内嵌函数必须引用外部函数中变量。

– 外部函数返回值必须是内嵌函数。

\2. 语法

-- 定义:

def 外部函数名(参数):

​		外部变量

​		def 内部函数名(参数):

​			使用外部变量

​		return 内部函数名

-- 调用:

​	  变量 = 外部函数名(参数)

​	  变量(参数)
"""
    应用
    价值:逻辑连续
"""


def give_gife_money(money):
    def child_buy(target, price):
        nonlocal money
        if price < money:
            money -= price
            print("购买了", target, "还剩下", money)
        else:
            print("钱不够")

    return child_buy
# 17:10

# 得到10000压岁钱
action = give_gife_money(10000)
action("变形金刚", 200)
action("电脑", 8000)
action("手机", 8848)

\3. 定义:

在一个函数内部的函数,同时内部函数又引用了外部函数的变量。

\4. 本质:

闭包是将内部函数和外部函数的执行环境绑定在一起的对象。

\5. 优点:

内部函数可以使用外部变量。

\6. 缺点:

外部变量一直存在于内存中,不会在调用结束后释放,占用内存。

\7. 作用:

实现python装饰器。

函数装饰器decorators

\1. 定义:在不改变原函数的调用以及内部代码情况下,为其添加新功能的函数。

"""
    练习:使用装饰器,为下列函数添加验证权限的功能.
"""
def verif_permissions(func):
    def wrapper(*args, **kwargs):
        print("验证权限")
        return func(*args, **kwargs)
    return wrapper

@verif_permissions
def enter_background():
    print("进入后台")

@verif_permissions
def delete_order():
    print("删除订单")

enter_background()
delete_order()

\2. 语法

def 函数装饰器名称(func):

  def 内嵌函数(*args, **kwargs):

​    需要添加的新功能

​    return func(*args, **kwargs)

return wrapper
 

@ 函数装饰器名称

def 原函数名称(参数):

​		函数体
 

原函数(参数)

\3. 本质:

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

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

\4. 装饰器链:

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

数据结构与算法

数据结构基本概念

什么是数据结构?

  1. 数据

数据即信息的载体,是能够输入到计算机中并且能被计算机识别、存储和处理的符号总称。

  1. 数据元素

数据元素是数据的基本单位,又称之为记录(Record)。一般数据元素由若干基本项组成。

  1. 数据结构

数据结构指的是数据元素及数据元素之间的相互关系,或组织数据的形式。

数据之间的结构关系

  1. 逻辑结构

表示数据之间的抽象关系(如邻接关系、从属关系等),按每个元素可能具有的直接前趋数和直接后继数将逻辑结构分为“线性结构”和“非线性结构”两大类。

  1. 存储结构

逻辑结构在计算机中的具体实现方法,分为顺序存储方法、链接存储方法、索引存储方法、散列存储方法。

逻辑结构(关系)

  1. 特点:
  • 只是描述数据结构中数据元素之间的联系规律
  • 是从具体问题中抽象出来的数学模型,是独立于计算机存储器的(与机器无关)
  1. 逻辑结构分类
  • 线性结构

对于数据结构课程而言,简单地说,线性结构是n个数据元素的有序(次序)集合。

  • 集合中必存在唯一的一个"第一个元素";
  • 集合中必存在唯一的一个"最后的元素";
  • 除最后元素之外,其它数据元素均有唯一的"后继";
  • 除第一元素之外,其它数据元素均有唯一的"前驱"。
  • 树形结构(层次结构)

树形结构指的是数据元素之间存在着“一对多”的树形关系的数据结构,是一类重要的非线性数据结构。在树形结构中,树根结点没有前驱结点,其余每个结点有且只有一个前驱结点。叶子结点没有后续结点,其余每个结点的后续节点数可以是一个也可以是多个。

  • 图状结构(网状结构)

图是一种比较复杂的数据结构。在图结构中任意两个元素之间都可能有关系,也就是说这是一种多对多的关系。

  • 其他结构

除了以上几种常见的逻辑结构外,数据结构中还包含其他的结构,比如集合等。有时根据实际情况抽象的模型不止是简单的某一种,也可能拥有更多的特征。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-29MJuqVB-1591240434743)(E:/BaiduNetdiskDownload/BaiduNetdisk/resource/Python1907-2001/02-DateStruct/数据结构/img/data1.png)]

存储结构(关系)

  1. 特点:
  • 是数据的逻辑结构在计算机存储器中的映象(或表示)
  • 存储结构是通过计算机程序来实现的,因而是依赖于具体的计算机语言的。
  1. 基础存储结构
  • 顺序存储

顺序存储(Sequential Storage):将数据结构中各元素按照其逻辑顺序存放于存储器一片连续的存储空间中。

  • 链式存储

链式存储(Linked Storage):将数据结构中各元素分布到存储器的不同点,用记录下一个结点位置的方式建立它们之间的联系,由此得到的存储结构为链式存储结构。

线性表

线性表的定义是描述其逻辑结构,而通常会在线性表上进行的查找、插入、删除等操作。
线性表作为一种基本的数据结构类型,在计算机存储器中的存储一般有两种形式,一种是顺序存储,一种是链式存储。

线性表的顺序存储

  1. 定义

若将线性表L=(a0,a1, ……,an-1)中的各元素依次存储于计算机一片连续的存储空间,这种机制表示为线性表的顺序存储结构。

  1. 特点
  • 逻辑上相邻的元素 ai, ai+1,其存储位置也是相邻的;
  • 存储密度高,方便对数据的遍历查找。
  • 对表的插入和删除等运算的效率较差。
  1. 程序实现

在Python中,list存放于一片单一连续的内存块,故可借助于列表类型来描述线性表的顺序存储结构,而且列表本身就提供了丰富的接口满足这种数据结构的运算。

>>>L = [1,2,3,4]
>>>L.append(10)      #尾部增加元素
L
[1, 2, 3, 4, 10]

>>>L.insert(1,20)    #插入元素
L
[1, 20, 2, 3, 4, 10]

>>>L.remove(3)       #删除元素
L
[1, 20, 2, 4, 10]     

>>>L[4] = 30         #修改
L
[1, 20, 2, 4, 30]

>>>L.index(2)        #查找
2

线性表的链式存储

  1. 定义

将线性表L=(a0,a1,……,an-1)中各元素分布在存储器的不同存储块,称为结点,每个结点(尾节点除外)中都持有一个指向下一个节点的引用,这样所得到的存储结构为链表结构。
在这里插入图片描述

  1. 特点
  • 逻辑上相邻的元素 ai, ai+1,其存储位置也不一定相邻;
  • 存储稀疏,不必开辟整块存储空间。
  • 对表的插入和删除等运算的效率较高。
  • 逻辑结构复杂,不利于遍历。
  1. 程序实现
"""
linklist.py
功能: 使用节点作为数据元素的生成器,对数据元素之间的关系进行构建和操作

重点代码
"""

# 创建节点类
class Node:
    """
    包含一个简单的数字作为数据
    next 构建关系
    """
    def __init__(self,val,next=None):
        self.val = val  #  有用数据
        self.next = next # 节点关系

"""
1. 构建节点间关系
2. 在节点中存储数据
3. 对单链表进行节点操作
"""

# 单链表的类
class LinkList:
    """
    思路: 生成对象即表示一个单链表对象
         对象调用方法可以完成对单链表的各种操作
    """
    def __init__(self):
        """
        初始化时 创建一个无用的节点,让对象拥有该节点,以表达链表的开端
        """
        self.head = Node(None)

    # 初始化
    def init_list(self,iter):
        p = self.head
        for i in iter:
            p.next = Node(i)
            p = p.next

    # 遍历打印
    def show(self):
        p = self.head.next # 第一个有效节点
        while p is not None:
            print(p.val)
            p = p.next   # p向后移动

    # 判断链表是否为空
    def is_empty(self):
        return self.head.next is None

    # 清空链表
    def clear(self):
        self.head.next = None

    # 尾部插入
    def append(self,val):
        p = self.head
        # p 移动到最后一个节点
        while p.next is not None:
            p = p.next
        p.next = Node(val)

    # 头部插入
    def head_insert(self,val):
        node = Node(val)
        node.next = self.head.next
        self.head.next = node

    # 指定位置插入
    def insert(self,index,val):
        p = self.head
        # 将p移动到待插入位置的前一个
        for i in range(index):
            # 超出最大范围
            if p.next is None:
                break
            p = p.next

        node = Node(val)
        node.next = p.next
        p.next = node

    # 删除节点(删除第一个val值)
    def delete(self,val):
        p = self.head
        # 确定p的位置(停留在待删除节点的前一个)
        while p.next and p.next.val != val:
            p = p.next

        # 分情况讨论
        if p.next is None:
            raise ValueError("x not in link")
        else:
            p.next = p.next.next

    # 获取节点值
    def get_value(self,index):
        if index < 0:
            raise IndexError('link index out of range')
        p = self.head.next
        for i in range(index):
            if p.next is None:
                raise IndexError('link index out of range')
            p = p.next
        return p.val



if __name__ == '__main__':
    link = LinkList()
    link.init_list(range(6))
    # link.show()
    # link.append(5)
    # link.show()
    # link.head_insert(9)
    # link.show()
    # link.insert(2,10)
    # link.show()
    # link.delete(8)
    # link.show()
    print(link.get_value(5))

# node1 = Node(1)
# link.head.next = node1
#
# node2 = Node(2)
# node1.next = node2
#
# node3 = Node(3)
# node2.next = node3

顺序结构和链式结构比较

1. 顺序结构
   优点 : 查找遍历方便
   缺点 : 数据量大的时候不利于存储
         不利于从中间插入数据
2. 链式结构
   优点: 数据存储分散,便于数据的插入删除操作
   缺点: 结构设计相对复杂,遍历速度慢

栈和队列

  1. 定义

栈是限制在一端进行插入操作和删除操作的线性表(俗称堆栈),允许进行操作的一端称为“栈顶”,另一固定端称为“栈底”,当栈中没有元素时称为“空栈”。

  1. 特点:
  • 栈只能在一端进行数据操作
  • 栈模型具有先进后出或者叫做后进先出的规律

在这里插入图片描述

  1. 栈的代码实现

栈的操作有入栈(压栈),出栈(弹栈),判断栈的空满等操作。

顺序存储代码实现:
"""
sstack.py  栈模型的顺序存储
重点代码

思路:
1. 利用列表完成顺序存储,但是列表功能多,不符合栈模型特点
2. 使用类将列表封装,提供符合栈特点的接口方法
"""

# 自定义异常
class StackError(Exception):
    pass

# 顺序栈模型
class SStack:
    def __init__(self):
        # 开辟一个顺序存储的模型空间
        # 列表的尾部表示栈顶
        self._elems = []

    # 判断栈是否为空
    def is_empty(self):
        return self._elems == []

    # 入栈
    def push(self,val):
        self._elems.append(val)

    # 出栈
    def pop(self):
        if self.is_empty():
            raise StackError("Stack is empty")
        # 弹出一个值并返回
        return self._elems.pop()

    # 查看栈顶元素
    def top(self):
        if self.is_empty():
            raise StackError("Stack is empty")
        return self._elems[-1]


if __name__ == '__main__':
    st = SStack()
    st.push(10)
    st.push(20)
    st.push(30)
    while not st.is_empty():
        print(st.pop())
链式存储代码实现:
"""
lstack.py 栈的链式结构
重点代码

思路:
1. 源于节点存储数据,建立节点关联
2. 封装方法 入栈 出栈 栈空 栈顶元素
3. 链表的开头作为栈顶(不需要每次遍历)
"""

# 自定义异常
class StackError(Exception):
    pass

# 创建节点类
class Node:
    def __init__(self,val,next=None):
        self.val = val  #  有用数据
        self.next = next # 节点关系

# 链式栈
class LStack:
    def __init__(self):
        # 标记顶位置
        self._top = None

    def is_empty(self):
        return self._top is None

    def push(self,val):
        self._top = Node(val,self._top)

    def pop(self):
        if self._top is None:
            raise StackError("Stack is empty")
        value = self._top.val
        self._top = self._top.next
        return value

    def top(self):
        if self._top is None:
            raise StackError("Stack is empty")
        return self._top.val


if __name__ == '__main__':
    ls = LStack()
    ls.push(10)
    ls.push(20)
    ls.push(30)
    while not ls.is_empty():
        print(ls.pop())

队列

  1. 定义

队列是限制在两端进行插入操作和删除操作的线性表,允许进行存入操作的一端称为“队尾”,允许进行删除操作的一端称为“队头”。

  1. 特点:
  • 队列只能在队头和队尾进行数据操作
  • 队列模型具有先进先出或者叫做后进后出的规律

在这里插入图片描述

  1. 队列的代码实现

队列的操作有入队,出队,判断队列的空满等操作。

顺序存储代码实现:
"""
squeue.py  队列的顺序存储

思路分析:
1. 基于列表完成数据的存储
2. 通过封装功能完成队列的基本行为
3. 无论那边做对头/队尾 都会在操作中有内存移动
"""

# 队列操作
class SQueue:
    def __init__(self):
        self._elems = []

    # 判断队列是否为空
    def is_empty(self):
        return self._elems == []

    # 入队
    def push(self,val):
        self._elems.append(val)

    # 出队
    def pop(self):
        if not self._elems:
            print(-1)
            return -1
        self._elems.pop(0) # 弹出第一个数据

    # 查看队首
    def top(self):
        if not self._elems:
            return -1
        return self._elems[0]

    def size(self):
        return len(self._elems)

    def clear(self):
        self._elems.clear()

if __name__ == '__main__':
    sq = SQueue()
    test_1 = int(input().strip())
    test_2 = int(input().strip())
    for i in range(test_1):
        for j in range(test_2):
            cmd = input().strip()
            if cmd[0:4]== 'PUSH':
                val = cmd.split(' ')[1]
                sq.push(val)
            elif cmd == 'TOP':
                print(sq.top())
            elif cmd == 'POP':
                sq.pop()
            elif cmd == 'CLEAR':
                sq.clear()
            elif cmd == 'SIZE':
                print(sq.size())
            else:
                pass
链式存储代码实现:
"""
lqueue.py 链式队列
重点代码

思路分析:
1. 基于链表构建队列模型
2. 链表的头作为队头,尾作为队尾
3. 定义两个标记标记队头和队尾
4. 头和尾代表同一个无用节点时队列为空
"""

# 自定义异常
class QueueError(Exception):
    pass

# 创建节点类
class Node:
    def __init__(self,val,next=None):
        self.val = val  #  有用数据
        self.next = next # 节点关系

# 队列操作
class LQueue:
    def __init__(self):
        # front 队头  rear 队尾
        self.front = self.rear = Node(None)

    def is_empty(self):
        return self.front == self.rear

    def enqueue(self,val):
        self.rear.next = Node(val)
        self.rear = self.rear.next

    def dequeue(self):
        if self.is_empty():
            raise QueueError("Queue is empty")
        # front 是指向队头节点的前一个
        self.front = self.front.next
        return self.front.val

if __name__ == '__main__':
    lq = LQueue()
    lq.enqueue(10)
    lq.enqueue(20)
    lq.enqueue(30)
    while not lq.is_empty():
        print(lq.dequeue())

树形结构

基础概念

  1. 定义

树(Tree)是n(n≥0)个节点的有限集合T,它满足两个条件:有且仅有一个特定的称为根(Root)的节点;其余的节点可以分为m(m≥0)个互不相交的有限集合T1、T2、……、Tm,其中每一个集合又是一棵树,并称为其根的子树(Subtree)。

在这里插入图片描述

  1. 基本概念
  • 一个节点的子树的个数称为该节点的度数,一棵树的度数是指该树中节点的最大度数。
  • 度数为零的节点称为树叶或终端节点,度数不为零的节点称为分支节点。
  • 一个节点的子树之根节点称为该节点的子节点,该节点称为它们的父节点,同一节点的各个子节点之间称为兄弟节点。一棵树的根节点没有父节点,叶节点没有子节点。
  • 节点的层数等于父节点的层数加一,根节点的层数定义为一。树中节点层数的最大值称为该树的高度或深度。

在这里插入图片描述

二叉树

定义与特征
  1. 定义

二叉树(Binary Tree)是n(n≥0)个节点的有限集合,它或者是空集(n=0),或者是由一个根节点以及两棵互不相交的、分别称为左子树和右子树的二叉树组成。二叉树与普通有序树不同,二叉树严格区分左孩子和右孩子,即使只有一个子节点也要区分左右。

在这里插入图片描述

  1. 二叉树的特征
  • 二叉树第i(i≥1)层上的节点最多为 2 i − 1 2^{i-1} 2i1个。

  • 深度为k(k≥1)的二叉树最多有 2 k - 1 2^k-1 2k1个节点。

  • 在任意一棵二叉树中,树叶的数目比度数为2的节点的数目多一。

  • 满二叉树 :深度为k(k≥1)时有 2 k - 1 2^k-1 2k1个节点的二叉树。

二叉树的遍历

遍历 :沿某条搜索路径周游二叉树,对树中的每一个节点访问一次且仅访问一次。

先序遍历: 先访问树根,再访问左子树,最后访问右子树;
中序遍历: 先访问左子树,再访问树根,最后访问右子树;
后序遍历: 先访问左子树,再访问右子树,最后访问树根;
层次遍历: 从根节点开始,逐层从左向右进行遍历。

递归思想和实践
  1. 什么是递归?

所谓递归函数是指一个函数的函数体中直接调用或间接调用了该函数自身的函数。这里的直接调用是指一个函数的函数体中含有调用自身的语句,间接调用是指一个函数在函数体里有调用了其它函数,而其它函数又反过来调用了该函数的情况。

  1. 递归函数调用的执行过程分为两个阶段

递推阶段:从原问题出发,按递归公式递推从未知到已知,最终达到递归终止条件。
回归阶段:按递归终止条件求出结果,逆向逐步代入递归公式,回归到原问题求解。

  1. 优点与缺点

优点:递归可以把问题简单化,让思路更为清晰,代码更简洁
缺点:递归因系统环境影响大,当递归深度太大时,可能会得到不可预知的结果

递归示例:
"""
就一个数n的阶乘
"""

# def recursion(n):
#     resule = 1
#     for i in range(1,n+1):
#         resule *= i
#     return resule

# 递归函数
def recursion(n):
    # 递归的终止条件
    if n <= 1:
        return 1
    return n * recursion(n - 1)

print(recursion(3))

二叉树的代码实现

二叉树顺序存储

二叉树本身是一种递归结构,可以使用Python list 进行存储。但是如果二叉树的结构比较稀疏的话浪费的空间是比较多的。

  • 空结点用None表示
  • 非空二叉树用包含三个元素的列表[d,l,r]表示,其中d表示根结点,l,r左子树和右子树。
['A',['B',None,None
     ],
     ['C',['D',['F',None,None],
               ['G',None,None],
          ],     
          ['E',['H',None,None],
               ['I',None,None],
          ],
     ]
]

在这里插入图片描述

二叉树链式存储
二叉树遍历:
"""
bitree.py 二叉树的实现

思路分析:
1. 使用链式存储, Node表达一个节点(值,左链接,右链接)
2. 分析遍历过程
"""


# 二叉树节点
class Node:
    def __init__(self, val, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


# 二叉树遍历
class Bitree:
    # 传入树根
    def __init__(self, root):
        self.root = root

    # 先序遍历
    def preOrder(self, node):
        if node is None:
            return
        print(node.val, end=' ')
        self.preOrder(node.left)
        self.preOrder(node.right)

    # 中序遍历
    def inOrder(self, node):
        if node is None:
            return
        self.inOrder(node.left)
        print(node.val, end=' ')
        self.inOrder(node.right)

    # 后序遍历
    def postOrder(self, node):
        if node is None:
            return
        self.postOrder(node.left)
        self.postOrder(node.right)
        print(node.val, end=' ')

    # 层次遍历
    def levelOrder(self, node):
        import day02.lqueue
        lq = day02.lqueue.LQueue()
        # 初始节点先入队,循环判断,队列不为空则出队
        # 出队元素的左右孩子分别入队
        lq.enqueue(node)
        while not lq.is_empty():
            # 出队,打印表示遍历
            node = lq.dequeue()
            print(node.val, end=' ')
            if node.left:
                lq.enqueue(node.left)
            if node.right:
                lq.enqueue(node.right)


if __name__ == '__main__':
    # B F G D H I E C A
    # 构建起一个二叉树
    b = Node('B')
    f = Node('F')
    g = Node("G")
    d = Node('D', f, g)
    h = Node('H')
    i = Node('I')
    e = Node('E', h, i)
    c = Node('C', d, e)
    a = Node('A', b, c)  # 树根

    bt = Bitree(a)

    bt.preOrder(bt.root)
    print()
    bt.inOrder(bt.root)
    print()
    bt.levelOrder(bt.root)

算法基础

基础概念特征

  1. 定义

算法是一个有穷规则(或语句、指令)的有序集合。它确定了解决某一问题的一个运算序列。对于问题的初始输入,通过算法有限步的运行,产生一个或多个输出。

数据的逻辑结构与存储结构密切相关:

  • 算法设计: 取决于选定的逻辑结构
  • 算法实现: 依赖于采用的存储结构
  1. 算法的特性
  • 有穷性 —— 算法执行的步骤(或规则)是有限的;
  • 确定性 —— 每个计算步骤无二义性;
  • 可行性 —— 每个计算步骤能够在有限的时间内完成;
  • 输入 ,输出 —— 存在数据的输入和出输出
  1. 评价算法好坏的方法
  • 正确性:运行正确是一个算法的前提。
  • 可读性:容易理解、容易编程和调试、容易维护。
  • 健壮性:考虑情况全面,不容以出现运行错误。
  • 时间效率高:算法消耗的时间少。
  • 储存量低:占用较少的存储空间。

时间复杂度计算

算法效率——用依据该算法编制的程序在计算机上执行所消耗的时间来度量。“O”表示一个数量级的概念。根据算法中语句执行的最大次数(频度)来 估算一个算法执行时间的数量级。

计算方法:

写出程序中所有运算语句执行的次数,进行加和
如果得到的结果是常量则时间复杂度为1
如果得到的结果中存在变量n则取n的最高次幂作为时间复杂度

排序和查找

排序

排序(Sort)是将无序的记录序列(或称文件)调整成有序的序列。排序方法有很多种,下面举例说明:

  • 冒泡排序

冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。

  • 快速排序

步骤:

从数列中挑出一个元素,称为 “基准”(pivot),
重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

常见排序代码实现:
"""
sort.py 排序训练
"""

# 冒泡
def bubble(l):
    n = len(l)
    # 循环n - 1 次,每次确定一个最大值
    for i in range(n - 1):
        # 两两比较交换
        for j in range(n - 1 - i):
            if l[j] > l[j + 1]:
                l[j],l[j + 1] = l[j + 1],l[j]


l = [4,5,7,1,2,9,6,8]
bubble(l)
print(l)
查找

查找(或检索)是在给定信息集上寻找特定信息元素的过程。

二分法查找

当数据量很大适宜采用该方法。采用二分法查找时,数据需是排好序的。

二分查找代码实现:
"""
二分查找实现
"""

# 从l中找到key的索引号
def search(l,key):
    low,high = 0,len(l) - 1

    while low <= high:
        # 中间数的索引
        mid = (low + high) // 2
        if l[mid] < key:
            low = mid + 1
        elif l[mid] > key:
            high = mid - 1
        else:
            return mid


l = [1,2,3,4,5,6,7,8,9,10,11]
print("Key index:",search(l,11))

IO网络编程

Linux 操作系统及其组成

  1. 操作系统的作用

操作系统(OS)是管理计算机硬件与软件资源的计算机程序,同时也是计算机系统的内核与基石。操作系统需要处理如管理与配置内存、决定系统资源供需的优先次序、控制输入设备与输出设备、操作网络与管理文件系统等基本事务。操作系统也提供一个让用户与系统交互的操作界面。

  1. Linux操作系统组成

一个典型的Linux操作系统组成为:Linux内核,文件系统,命令行shell,图形界面和桌面环境,并包各种工具和应用软件。

  • Linux内核: Linux操作系统的核心代码

  • 文件系统:通常指称管理磁盘数据的系统,可将数据以目录或文件的型式存储。每个文件系统都有自己的特殊格式与功能

  • shell命令: 接收用户命令,然后调用相应的应用程序,并根据用户输入的指令来反馈给用户指定的信息。

在这里插入图片描述

shell命令

文件操作命令
  • linux下的目录结构

在这里插入图片描述

作用命令
切换工作目录cd
查看文件ls , ls -l , ls -a
复制文件cp -r
移动文件mv
删除文件rm -rf , rmdir
创建文件夹mkdir -p
创建文件touch
查看文件内容cat

IO

  1. 定义

在内存中存在数据交换的操作认为是IO操作,比如和终端交互 ,和磁盘交互,和网络交互等

  1. 程序分类
  • IO密集型程序:在程序执行中有大量IO操作,而cpu运算较少。消耗cpu较少,耗时长。
  • 计算密集型程序:程序运行中计算较多,IO操作相对较少。cpu消耗多,执行速度快,几乎没有阻塞。
文件

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

字节串(bytes)

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

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

对文件实现读写的基本操作步骤为:打开文件,读写文件,关闭文件

"""
编写一个文件拷贝程序,将一个文件拷贝一份,重新取另外一个名字(自定).
      文件可能是文本,也可能是二进制
"""

filename = input("文件:")

fr = open(filename,'rb')
fw = open("备份-"+filename,'wb')

while True:
    # 循环读取
    data = fr.read(1024)
    if not data:  # 文件结束
        break
    fw.write(data)

fr.close()
fw.close()
打开文件
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表示有行缓冲,默认则表示使用系统默认提供的缓冲机制。
返回值:成功返回文件操作对象。
"""
file_open.py
文件打开方式训练
"""

# 打开文件
try:
    """
    文本文件既可以使用文本方式打开,也能使用二进进制方式打开
    二进制文件如果使用文本方式打开,读写时会报错
    """
    # f = open('text.py','r') # 只读方式
    # f = open('text.py','w') # 只写方式
    f = open('text.py','ab') # 追加方式
except Exception as e:
    print(e)

# 读写文件

# 关闭文件
f.close()
读取文件

read([size])
功能: 来直接读取文件中字符。
参数: 如果没有给定size参数(默认值为-1)或者size值为负,文件将被读取直至末尾,给定size最多读取给定数目个字符(字节)。
返回值: 返回读取到的内容

  • 注意:文件过大时候不建议直接读取到文件结尾,读到文件结尾会返回空字符串。

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

readlines([sizeint])
功能: 读取文件中的每一行作为列表中的一项
参数: 如果没有给定size参数(默认值为-1)或者size值为负,文件将被读取直至末尾,给定size表示读取到size字符所在行为止。
返回值: 返回读取到的内容列表

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

"""
file_read.py
文件读取演示
"""

# 打开文件
f = open('4.txt','rb')

# 读操作

while True:
    # 到文件结尾时会读出空字串
    data = f.read(16)
    # 到文件结尾跳出循环
    if not data:
        break
    print("读取到的数据:",data)

# 每次读取一行内容
# data = f.readline(6)
# print("一行内容:",data)
# data = f.readline()
# print("一行内容:",data)

# 将内容读取到一个列表
# 参数表达的是读取到该字符数所在的行
# data = f.readlines(28)
# print(data)

# 文件对象可迭代,每次一行
# for line in f:
    # print(line)

# 关闭
f.close()
写入文件

write(string)
功能: 把文本数据或二进制数据块的字符串写入到文件中去
参数:要写入的内容
返回值:写入的字符个数

  • 如果需要换行要自己在写入内容中添加\n

writelines(str_list)
功能:接受一个字符串列表作为参数,将它们写入文件。
参数: 要写入的内容列表

  • 若不设置文件偏移量、会覆盖已有内容
"""
file_write.py
文件写操作演示
"""

f = open('4.txt','w')
# f = open('text','ab')

# 写操作
# f.write(b"hello,diegui\n") # 如果希望换行则自己添加
# f.write("哎呀,干啥".encode())

# 写入列表内容
l = ['hello world\n','hello world\n']
f.writelines(l)

f.close()
关闭文件

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

file_object.close()

with操作

python中的with语句使用于对资源进行访问的场合,保证不管处理过程中是否发生错误或者异常都会执行规定的“清理”操作,释放被访问的资源,比如有文件读写后自动关闭、线程中锁的自动获取和释放等。

with语句的语法格式如下:

with context_expression [as obj]:
    with-body

通过with方法可以不用close(),因为with生成的对象在语句块结束后会自动处理,所以也就不需要close了,但是这个文件对象只能在with语句块内使用。

with open('file','r+') as f:
    f.read()

注意

  1. 加b的打开方式读写要求必须都是字节串
  2. 无论什么文件都可以使用二进制方式打开,但是二进制文件使用文本方式打开读写会出错
其他操作
刷新缓冲区

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

刷新缓冲区条件:

  1. 缓冲区被写满
  2. 程序执行结束或者文件对象被关闭
  3. 行缓冲遇到换行
  4. 程序中调用flush()函数

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

"""
buffer.py
缓冲区演示
"""

# f = open('test','w',1) # 行缓冲
f = open('test','w')

while True:
    data = input(">>")
    if not data:
        break
    f.write(data + '\n')
    f.flush()  # 主动刷新缓冲

f.close()
文件偏移量
"""
seek.py  文件偏移量

注意: 1. open打开文件会重置文件偏移量
     2. 读写操作使用的是一个偏移量值
     3. seek操作文件偏移量一般是以二进制打开
"""

f = open("test",'wb+')

f.write(b"Hello world")
# f.close()
#
# f = open('test','r')

print("偏移量:",f.tell()) # 获取文件偏移量

f.seek(-5,2)  # 将文件偏移量定位到开头
data = f.read()
print(data)

f.close()
  1. 定义

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

  1. 基本操作

tell()
功能:获取文件偏移量大小

seek(offset[,whence])
功能:移动文件偏移量位置
参数:

offset 代表相对于某个位置移动的字节数。负数表示向前移动,正数表示向后移动。
whence 是基准位置的默认值为 0,代表从文件开头算起,1代表从当前位置算起,2 代表从文件末尾算起。

  • 必须以二进制方式打开文件时基准位置才能是1或者2
文件描述符
  1. 定义

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

  1. 获取文件描述符

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

文件管理函数
  1. 获取文件大小

os.path.getsize(file)

  1. 查看文件列表

os.listdir(dir)

  1. 查看文件是否存在

os.path.exists(file)

  1. 判断文件类型

os.path.isfile(file)

  1. 删除文件

os.remove(file)

网络编程基础

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

OSI七层模型

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

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

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

优点

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

四层模型(TCP/IP模型)

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

在这里插入图片描述

数据传输过程

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

在这里插入图片描述

网络协议

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

网络基础概念

  • IP地址

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

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

结构

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

  • 域名

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

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

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

  • 端口号(port)

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

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

取值范围: 1 – 65535

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

传输层服务

面向连接的传输服务(基于TCP协议的数据传输)
  1. 传输特征 : 提供了可靠的数据传输,可靠性指数据传输过程中无丢失,无失序,无差错,无重复。

  2. 实现手段 : 在通信前需要建立数据连接,通信结束要正常断开连接。

三次握手(建立连接)

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

在这里插入图片描述

四次挥手(断开连接)

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

在这里插入图片描述

  1. 适用情况 : 对数据传输准确性有明确要求,传数文件较大,需要确保可靠性的情况。比如:网页获取,文件下载,邮件收发。
面向无连接的传输服务(基于UDP协议的数据传输)
  1. 传输特点 : 不保证传输的可靠性,传输过程没有连接和断开,数据收发自由随意。

  2. 适用情况 : 网络较差,对传输可靠性要求不高。比如:网络视频,群聊,广播

面试要求
  • OSI七层模型介绍一下,tcp/ip模型是什么?
  • tcp服务和udp服务有什么区别?
  • 三次握手和四次挥手指什么,过程是怎样的?

socket套接字编程

套接字介绍

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

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

  3. 套接字分类

流式套接字(SOCK_STREAM): 以字节流方式传输数据,实现tcp网络传输方案。(面向连接–tcp协议–可靠的–流式套接字)

数据报套接字(SOCK_DGRAM):以数据报形式传输数据,实现udp网络传输方案。(无连接–udp协议–不可靠–数据报套接字)

tcp套接字编程

服务端流程

在这里插入图片描述

  1. 创建套接字
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’

在这里插入图片描述

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

n = connfd.send(data)
功能 : 发送消息
参数 :要发送的内容  bytes格式
返回值: 发送的字节数
  1. 关闭套接字
sockfd.close()
功能:关闭套接字
代码实现
"""
tcp_server.py  tcp套接字服务端流程
重点代码

注意: 功能性代码,注重流程和函数使用
"""

import socket

# 创建tcp套接字对象
sockfd = socket.socket(socket.AF_INET,
                       socket.SOCK_STREAM)

# 绑定地址
sockfd.bind(('0.0.0.0',9999))

# 设置监听
sockfd.listen(5)

# 等待处理客户端连接请求
print("Waiting for connect...")
connfd,addr = sockfd.accept()
print("Connect from",addr)

# 消息收发
data = connfd.recv(1024)
print("Receive:",data.decode())
n = connfd.send(b"Thanks")
print('Send %d bytes'%n)

# 关闭套接字
connfd.close()
sockfd.close()
客户端流程

在这里插入图片描述

  1. 创建套接字

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

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

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

  1. 关闭套接字
代码实现
"""
tcp_client.py  tcp套接字客户端流程
重点代码

注意: 和服务端配合,使用同样的套接字
"""

from socket import *

# 创建tcp套接字
sockfd = socket() # 默认值

# 连接服务器
server_addr = ('127.0.0.1',9999) # 服务器地址
sockfd.connect(server_addr)

# 先发后收
msg = input("Msg:")
sockfd.send(msg.encode()) #字节串
data = sockfd.recv(1024)
print("From server:",data.decode())

sockfd.close()
tcp 套接字数据传输特点
  • tcp连接中当一端退出,另一端如果阻塞在recv,此时recv会立即返回一个空字串。
  • tcp连接中如果一端已经不存在,仍然试图通过send发送则会产生BrokenPipeError
  • 一个监听套接字可以同时连接多个客户端,也能够重复被连接
网络收发缓冲区
  1. 网络缓冲区有效的协调了消息的收发速度
  2. send和recv实际是向缓冲区发送接收消息,当缓冲区不为空recv就不会阻塞。
tcp粘包

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

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

处理方法

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

UDP套接字编程

服务端流程

在这里插入图片描述

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

n = sockfd.sendto(data,addr)
功能: 发送UDP消息
参数: data  发送的内容 bytes格式
	addr  目标地址
返回值:发送的字节数
  1. 关闭套接字
sockfd.close()
代码实现
"""
udp_server.py  udp套接字服务端流程
重点代码
"""

from socket import *

# 创建udp套接字
sockfd = socket(AF_INET,SOCK_DGRAM)

# 绑定地址
server_addr = ('127.0.0.1',8888)
sockfd.bind(server_addr)

# 循环收发消息
while True:
    data,addr = sockfd.recvfrom(1024)
    print("Msg from %s: %s"%(addr,data.decode()))
    sockfd.sendto(b'Thanks',addr)

# 关闭套接字
sockfd.close()
客户端流程

在这里插入图片描述

  1. 创建套接字
  2. 收发消息
  3. 关闭套接字
代码实践:
"""
udp_client.py udp客户端
重点代码
"""

from socket import *

# 服务器地址
ADDR = ("127.0.0.1",8888)

# 创建套接字
sockfd = socket(AF_INET,SOCK_DGRAM)

# 循环收发消息
while True:
    data = input("Msg>>")
    if not data: # 退出
        break
    sockfd.sendto(data.encode(),ADDR)
    msg,addr = sockfd.recvfrom(1024)
    print("From server:",msg.decode())

sockfd.close()

TCP、UDP套接字总结


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

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

socket套接字属性

【1】 sockfd.type 套接字类型

【2】 sockfd.family 套接字地址类型

【3】 sockfd.getsockname() 获取套接字绑定地址

【4】 sockfd.fileno() 获取套接字的文件描述符

0、1、2被占用,从3开始

【5】 sockfd.getpeername() 获取连接套接字客户端地址

【6】 sockfd.setsockopt(level,option,value)
功能:设置套接字选项
参数: level 选项类别 SOL_SOCKET
option 具体选项内容
value 选项值

在这里插入图片描述

struct模块进行数据打包

  1. 原理: 将一组简单数据进行打包,转换为bytes格式发送。或者将一组bytes格式数据,进行解析。
  2. 接口使用
Struct(fmt)
功能: 生成结构化对象 
参数:fmt  定制的数据结构

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

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

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

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

​ 3.格式符

在这里插入图片描述

import struct
from socket import *

# 设置数据结构
st = struct.Struct("i16sif")

# 创建udp套接字
s = socket(AF_INET,SOCK_DGRAM)
ADDR = ('127.0.0.1',8888)

while True:
    print("===========================")
    id = int(input("学号:"))
    name = input("姓名:").encode()
    age = int(input("年龄:"))
    score = float(input("得分:"))
    # 将数据打包
    data = st.pack(id,name,age,score)
    s.sendto(data,ADDR) # 发送给服务端

s.close()
"""
 udp完成 ,从客户端输入学生信息,循环录入

          学号:
          姓名: 不会超过16字节
          年龄:
          分数: 保留一位小数

          将信息发送给服务端,在服务端写入到一个文件里,
          每个学生信息占一行
"""

from socket import *
import struct

# 与客户端格式一直
st = struct.Struct("i16sif")

#udp套接字
s = socket(AF_INET,SOCK_DGRAM)
s.bind(('127.0.0.1',8888))

# 打开一个保存信息的文件
f = open('student.txt','a')

while True:
    data,addr = s.recvfrom(1024)
    # (1,b'Lily',14,94.5)
    data = st.unpack(data)

    # 写入文件
    info = "%d  %-10s  %d  %.1f\n"%data
    f.write(info)
    f.flush()

f.close()
s.close()

HTTP传输

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

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

  2. 特点

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

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

在这里插入图片描述

HTTP请求(request)

  • 请求行 : 具体的请求类别和请求内容
	GET         /        HTTP/1.1
	请求类别   请求内容     协议版本

请求类别:每个请求类别表示要做不同的事情

		GET : 获取网络资源
		POST :提交一定的信息,得到反馈
		HEAD : 只获取网络资源的响应头
		PUT : 更新服务器资源
		DELETE : 删除服务器资源
		CONNECT
		TRACE : 测试
		OPTIONS : 获取服务器性能信息
  • 请求头:对请求的进一步解释和描述
Accept-Encoding: gzip
  • 空行
  • 请求体: 请求参数或者提交内容

http响应(response)

  1. 响应格式:响应行,响应头,空行,响应体
  • 响应行 : 反馈基本的响应情况
HTTP/1.1     200       OK
版本信息    响应码   附加信息

响应码 :

1xx  提示信息,表示请求被接收
2xx  响应成功
3xx  响应需要进一步操作,重定向
4xx  客户端错误
5xx  服务器错误
  • 响应头:对响应内容的描述
Content-Type: text/html
  • 响应体:响应的主体内容信息

代码实践

"""
编写一个http服务端程序
     如果浏览器的请求内容 /
     响应码为  200  OK,将index.html内容作为响应内容

     如果浏览器的请求是其他的
     响应码为  404  Not Found  内容为 "Sorry.."
"""
from socket import *

# 与客户端交互
def handle(connfd):
    # 获取http请求
    data = connfd.recv(4096).decode()
    request_line = data.split('\n')[0] # 请求行
    info = request_line.split(' ')[1] # 请求内容
    # 看一下请求内容是不是/
    if info == '/':
        with open('index.html') as f:
            # 组织http响应
            response = "HTTP/1.1 200 OK\r\n"
            response += "Content-Type:text/html\r\n"
            response += '\r\n'
            response += f.read()
    else:
        response = "HTTP/1.1 404 Not Found\r\n"
        response += "Content-Type:text/html\r\n"
        response += '\r\n'
        response += "<h1>Sorry...</h1>"
    # 发送给浏览器
    connfd.send(response.encode())

# 搭建网络
def main():
    sockfd = socket()
    sockfd.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
    sockfd.bind(('0.0.0.0',8000))
    sockfd.listen(3)
    while True:
        connfd,addr = sockfd.accept()
        print("Connect from",addr)
        # 处理客户端请求
        handle(connfd)

main()
"""
http请求响应演示
"""

from socket import *

# tcp服务端
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) # 端口立即重用
s.bind(('127.0.0.1',8000))
s.listen(5)

c,addr = s.accept()
print("Connect from",addr)
data = c.recv(4096).decode()
print(data)  # http请求

html = """HTTP/1.1 200 OK
Content-Type: text/html

<h1>Hello World</h1>
"""
c.send(html.encode())

c.close()
s.close()

并发编程<进程、线程>

多任务编程

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

  2. 实现方案 :多进程 , 多线程

进程(process)

进程理论基础

  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】 每个进程拥有独立的空间,各自使用自己空间资源

面试要求

  1. 什么是进程,进程和程序有什么区别
  2. 进程有哪些状态,状态之间如何转化

基于fork的多进程编程

fork使用(Windows不支持)

pid = os.fork()

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

注意

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

import os
from time import sleep

# 创建子进程
pid = os.fork()
if pid < 0:
    print("Create process failed")
elif pid == 0:
    # 子进程执行部分
    sleep(3)
    print("The new process")
else:
    # 父进程执行部分
    sleep(2)
    print("The old process")

print("Fork test over") # 父子进程都执行
"""
fork1.py  fork进程演示示例
"""

import os
from time import sleep

print("============================")
a = 1

pid = os.fork()

if pid < 0:
    print("Error")
elif pid == 0:
    print("Child process")
    print("a = ",a) # 从父进程空间获取的a
    a = 10000 # 修改自己空间的a
else:
    sleep(1)
    print("Parent process")
    print('a:',a)

print("all a = ",a) # 父子进程都执行

进程相关函数

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

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

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

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

孤儿和僵尸

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

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

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

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

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

import os
from time import sleep

pid = os.fork()
if pid < 0:
    print("Error")
elif pid == 0:
    print("Child process:",os.getpid())
    sleep(2)
    os._exit(3) # 进程退出
else:
    pid,status = os.wait() # 阻塞等待回收子进程
    print("pid:",pid)
    print("status:",os.WEXITSTATUS(status))
    while True: # 让父进程不退出
        pass
  • 创建二级子进程处理僵尸

    from time import sleep
    import os
    
    def f1():
        for i in range(3):
            sleep(2)
            print("写代码")
    
    def f2():
        for i in range(2):
            sleep(4)
            print("测代码")
    
    pid = os.fork()
    if pid == 0:  # 一级子进程
        p = os.fork()
        if p == 0:  # 二级子进程
            f1()
    else:
        os.wait()
        f2()  # 父进程事件
    

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

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

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

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

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

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

群聊聊天室

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

多线程带界面的群聊聊天室

client
"""
chat room  客户端
发送请求,展示结果
"""
from socket import *
import os,sys

# 服务器地址
ADDR = ('127.0.0.1',8888)

# 发送消息
def send_msg(s,name):
    while True:
        try:
            text = input(">>")
        except KeyboardInterrupt:
            text = 'quit'
        if text.strip() == 'quit':
            msg = "Q " + name
            s.sendto(msg.encode(),ADDR)
            sys.exit("退出聊天室")
        msg = "C %s %s"%(name,text)
        s.sendto(msg.encode(),ADDR)

# 接收消息
def recv_msg(s):
    while True:
        data,addr = s.recvfrom(4096)
        # 收到exit接收进程结束
        if data.decode() == 'EXIT':
            sys.exit()
        print(data.decode()+'\n>>',end='')

# 搭建网络
def main():
    s = socket(AF_INET,SOCK_DGRAM)

    # 进入聊天室
    while True:
        name = input("请输入昵称:")
        msg = "L " + name
        s.sendto(msg.encode(),ADDR)
        # 接收反馈
        data,addr = s.recvfrom(128)
        if data == b'OK':
            print("您已进入聊天室")
            break
        else:
            print(data.decode())

    # 已经进入聊天室
    pid = os.fork()
    if pid < 0:
        sys.exit("Error!")
    elif pid == 0:
        send_msg(s,name)
    else:
        recv_msg(s)

if __name__ == '__main__':
    main()
server
"""
chat room
env: python3.6
socket udp & fork exc
"""

from socket import *
import os, sys

# 全局变量:很多封装模块都要用或者有特定含义的变量
HOST = '0.0.0.0'
PORT = 8888
ADDR = (HOST,PORT)

# 存储用户 {name:address}
user = {}

# 处理用户登录
def do_login(s,name,addr):
    if name in user or '管理员' in name:
        s.sendto("用户名存在".encode(),addr)
        return
    else:
        s.sendto(b'OK',addr) # 可以进入
    # 通知其他人
    msg = "\n欢迎 %s 加入群聊"%name
    for i in user:
        s.sendto(msg.encode(),user[i])
    user[name] = addr # 加入字典

# 处理聊天
def do_chat(s,name,text):
    msg = "\n%s : %s"%(name,text)
    for i in user:
        # 不发送自己
        if i != name:
            s.sendto(msg.encode(),user[i])

# 处理退出
def do_quit(s,name):
    msg = "\n%s 退出了群聊"%name
    for i in user:
        if i == name:
            s.sendto(b'EXIT',user[i])
        else:
            s.sendto(msg.encode(),user[i])
    del user[name] # 删除用户

# 循环获取客户端请求
def do_request(s):
    while True:
        data,addr = s.recvfrom(1024)
        tmp = data.decode().split(' ',2)
        # 根据不同的请求类型,执行不同的事件
        if tmp[0] == 'L':
            do_login(s,tmp[1],addr)
        elif tmp[0] == 'C':
            do_chat(s,tmp[1],tmp[2])
        elif tmp[0] == 'Q':
            do_quit(s,tmp[1])

# 搭建网络
def main():
    # udp网络
    s = socket(AF_INET,SOCK_DGRAM)
    s.bind(ADDR)
    pid = os.fork()
    if pid == 0:
        # 管理员消息处理
        while True:
            msg = input("管理员消息:")
            msg = "C 管理员 "+ msg
            s.sendto(msg.encode(),ADDR)
    else:
        do_request(s) # 接收客户端请求

if __name__ == '__main__':
    main()

multiprocessing 模块创建进程

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

  2. 基本接口使用

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

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

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

注意

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

p.name 进程名称

p.pid 对应子进程的PID号

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

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

  • 如果设置为True则子进程会随父进程的退出而结束
  • 要求必须在start()前设置
  • 如果daemon设置成True 通常就不会使用 join()
代码示例
"""
同时创建多个子进程
"""

from multiprocessing import Process
from time import sleep
import os

def th1():
    sleep(3)
    print("吃饭")
    print(os.getppid(),'--',os.getpid())

def th2():
    # a = input()   # 不能使用标准输入
    sleep(2)
    print("睡觉")
    print(os.getppid(),'--',os.getpid())

def th3():
    sleep(4)
    print("打豆豆")
    print(os.getppid(),'--',os.getpid())

things = [th1,th2,th3]
jobs = []
for th in things:
    p = Process(target = th)
    jobs.append(p) # 列表存储一下进程对象
    p.start()

for i in jobs:
    i.join()
自定义进程类
  1. 创建步骤
    【1】 继承Process类
    【2】 重写__init__方法添加自己的属性,使用super()加载父类属性
    【3】 重写run()方法
  2. 使用方法
    【1】 实例化对象
    【2】 调用start自动执行run方法
    【3】 调用join回收线程
代码示例
"""
自定义进程类
"""
from multiprocessing import Process

# 自定义类
class MyProcess(Process):
    def __init__(self,value):
        self.value = value
        super().__init__() # 加载父类init

    def f1(self):
        print("步骤1")

    def f2(self):
        print("步骤2")

    def run(self):
        self.f1()
        self.f2()
        
if __name__ == '__main__':
    p = MyProcess(2)
    p.start() # 执行run,作为一个子进程执行
    p.join()

进程池实现

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

  2. 原理

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

  1. 进程池实现

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

from multiprocessing import Pool

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

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

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

【3】 关闭进程池

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

【4】 回收进程池中进程

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

代码示例

"""
进程池使用示例
"""

from multiprocessing import Pool
from time import sleep,ctime

# 进程池执行事件
def worker(msg):
    sleep(2)
    print(ctime(),'--',msg)
if __name__ == '__main__':
    # 创建进程池
    pool = Pool(4)

    # 添加时间
    for i in range(10):
        msg = "Tedu %d"%i
        pool.apply_async(func=worker,args=(msg,))

    # 关闭进程池
    pool.close()

    # 回收进程池
    pool.join()

进程间通信(IPC)

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

  2. 常用进程间通信方法

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

管道通信(Pipe)
  1. 通信原理

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

  1. 实现方法
from  multiprocessing import Pipe

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

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

fd.send(data)
功能: 向管道写入内容
参数: 要写入的数据
代码示例
"""
pipe.py 管道通信

注意: 管道对象需在父进程中创建,子进程从父进程中获取
"""

from multiprocessing import Process,Pipe

# 创建管道
# False单向管道 fd1->recv  fd2->send
# 不要在一个进程中同时使用fd1 fd2
fd1,fd2 = Pipe(False)

def app1():
    print("启动app1,请登录,(可以使用app2)")
    print("向app2发请求")
    fd1.send("app1需要:用户名,头像") # 写管道
    data = fd1.recv()
    print("Oh yeah",data)

def app2():
    data = fd2.recv() # 读管道
    print("app1请求:",data)
    fd2.send({'name':'Han','image':'有'})

p1 = Process(target=app1)
p2 = Process(target=app2)
p1.start()
p2.start()
p1.join()
p2.join()
消息队列

1.通信原理

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

  1. 实现方法
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()  关闭队列
代码示例
"""
使用进程池拷贝一个目录下所有的文件(普通文件),实时打印拷贝的百分比进度。
"""
from multiprocessing import Pool,Queue
import os

q = Queue() # 消息队列

# 拷贝文件
def copy_file(file,old_dir,new_dir):
    fr = open(old_dir+file,'rb')
    fw = open(new_dir+file,'wb')
    while True:
        data = fr.read(1024)
        if not data:
            break
        n = fw.write(data) # 写入的字节数
        q.put(n) # 放到消息队列

# 创建进程池
def main():
    #  确定好源目录和备份目录
    path = "/home/user/"
    dir = input("输入要拷贝的文件目录:")
    old_dir = path + dir + '/' #源目录绝对路径
    new_dir = path + dir + "-备份/"
    os.mkdir(new_dir)

    # 要拷贝的文件列表
    file_list = os.listdir(old_dir)

    # 获取文件总大小
    total_size = 0
    for file in file_list:
        total_size += os.path.getsize(old_dir+file)
    print("总共大小:%d M"%(total_size/1024//1024))

    # 添加进程池事件
    pool = Pool(4)
    for file in file_list:
        pool.apply_async(copy_file,args=(file,old_dir,new_dir))
    pool.close()

    copy_size = 0 # 已经拷贝的大小
    while copy_size < total_size:
        copy_size += q.get() # 获取队列数量值
        print("拷贝了%.1f%%"%(copy_size/total_size*100))
    pool.join()

if __name__ == '__main__':
    main()
共享内存
  1. 通信原理:在内中开辟一块空间,进程可以写入内容和读取内容完成通信,但是每次写入内容会覆盖之前内容。

  2. 实现方法

在这里插入图片描述

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直接打印共享内存中的字节串
示例value
"""
value.py 开辟共享内存
注意: 共享内存只能有一个值
"""

from multiprocessing import Process,Value
import time
from random import randint

# 创建共享内存
money = Value('i',5000)

# 操作内存
def man():
    for i in range(30):
        time.sleep(0.2)
        money.value += randint(1,1000)

def girl():
    for i in range(30):
        time.sleep(0.15)
        money.value -= randint(100,800)

if __name__ == '__main__':
    p1 = Process(target=man)
    p2 = Process(target=girl)
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    print("一个月余额:",money.value)
示例array
"""
array.py 存放一组数据
"""

from multiprocessing import Process,Array

# 共享内存,初始[1,2,3,4,5]
# shm = Array('i',[1,2,3,4,5])
# shm = Array('i',4) #共享内存,初始[0,0,0,0]
shm = Array('c',b'Hello')

def fun():
    # 迭代获取共享内存值
    for i in shm:
        print(i)
    shm[0] = b'h'

if __name__ == '__main__':
    p = Process(target = fun)
    p.start()
    p.join()
    # for i in shm:
    #     print(i)
    print(shm.value)  # 用于打印共享内存字节串
信号量(信号灯集)
  1. 通信原理

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

  1. 实现方法
from multiprocessing import Semaphore

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

sem.acquire()  将信号量减1 当信号量为0时阻塞
sem.release()  将信号量加1
sem.get_value() 获取信号量数量
示例
"""
sem.py  信号量演示
注意: 信号量相当于资源,多个进程对数量进行控制
"""

from multiprocessing import Process,Semaphore
from time import sleep
import os

# 创建信号量
sem = Semaphore(3)

# 任务函数
def handle():
    sem.acquire() # 执行任务必须消耗一个信号量
    print("开始执行任务:",os.getpid())
    sleep(2)
    print("执行任务结束:", os.getpid())
    sem.release() # 增加一个信号量
if __name__ == '__main__':
    for i in range(5):
        p = Process(target = handle)
        p.start()

多进程IO操作

注意: 
对IO操作来说,父进程打开一个IO,子进程从父进程获取IO对象,这是,父子进程实际操作的是同一个IO(fd相同)
如果各个进程各自打开IO操作,那么相互之间没有任何影响(fd不同)

线程编程(Thread)

线程基本概念

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

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

threading模块创建线程

【1】 创建线程对象

from threading import Thread 

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

【2】 启动线程

 t.start()

【3】 回收线程

 t.join([timeout])

线程对象属性

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

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

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

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

自定义线程类

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

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

同步互斥

线程间通信方法
  1. 通信方法

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

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

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

  1. 同步互斥机制

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

在这里插入图片描述

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

在这里插入图片描述

线程同步互斥方法
线程Event
from threading import Event

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

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

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

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

e.is_set()  查看当前e是否被设置



"""
event 线程互斥方法演示
"""

from threading import Thread,Event

s = None
e = Event()

def 杨子荣():
    print("杨子荣前来拜山头")
    global s
    s = "天王盖地虎"
    e.set()

t = Thread(target = 杨子荣)
t.start()

print("说对口令就是自己人")
e.wait() # 等待e被set
if s == '天王盖地虎':
    print("宝塔镇河妖")
    print("确认过眼神,你是对的人")
else:
    print("打死他!")

t.join()
线程锁 Lock
from  threading import Lock

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

with  lock:  上锁
...
...
	 with代码块结束自动解锁
死锁及其处理
  1. 定义

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

在这里插入图片描述

  1. 死锁产生条件

死锁发生的必要条件

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

死锁的产生原因

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

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

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

python线程GIL

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

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

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

GIL问题建议

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

进程线程的区别联系

区别联系

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

使用场景

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

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

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

要求

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

并发网络通信模型

常见网络模型

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

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

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

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

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

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

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

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

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

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

实现步骤
  1. 创建监听套接字
  2. 等待接收客户端请求
  3. 客户端连接创建新的进程处理客户端请求
  4. 原进程继续等待其他客户端连接
  5. 如果客户端退出,则销毁对应的进程
代码
"""
fork_server.py 基于fork多进程并发
重点代码

1. 创建监听套接字
2. 循环等待客户端连接
3. 客户端连接创建新的进程为客户端服务
4. 原进程继续等待其他客户端连接
5. 客户端退出,对应的进程也销毁
"""

from socket import *
import os
import signal

# 全局变量
HOST = '127.0.0.1'
PORT = 8888
ADDR = (HOST,PORT)

# 客户端处理
def handle(c):
    while True:
        data = c.recv(1024)
        if not data:
            break
        print(data.decode())
        c.send(b'OK')
    c.close()

# 创建套接字
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(ADDR)
s.listen(5)

# 处理僵尸进程
signal.signal(signal.SIGCHLD,signal.SIG_IGN)

print("Listen the port 8888...")
while True:
    # 循环等待客户端连接
    try:
        c,addr = s.accept()
        print("Connect from",addr)
    except KeyboardInterrupt as e:
        os._exit(0)
    except Exception as e:
        print(e)
        continue
    # 创建进程
    pid = os.fork()
    if pid == 0:
        s.close()
        handle(c) # 和客户端交互
        os._exit(0) # 客户端结束后,子进程结束
    else:
        c.close()

基于threading的多线程网络并发

实现步骤
  1. 创建监听套接字
  2. 循环接收客户端连接请求
  3. 当有新的客户端连接创建线程处理客户端请求
  4. 主线程继续等待其他客户端连接
  5. 当客户端退出,则对应分支线程退出
代码
"""
thread_server.py 多线程并发模型
重点代码

创建监听套接字
循环接收客户端连接请求
当有新的客户端连接创建线程处理客户端请求
主线程继续等待其他客户端连接
当客户端退出,则对应分支线程退出
"""
from socket import *
from threading import Thread
import sys

# 全局变量
HOST = '127.0.0.1'
PORT = 8888
ADDR = (HOST,PORT)

# 客户端处理
def handle(c):
    while True:
        data = c.recv(1024)
        if not data:
            break
        print(data.decode())
        c.send(b'OK')
    c.close()

# 创建套接字
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(ADDR)
s.listen(5)

print("Listen the port 8888...")
while True:
    # 循环等待客户端连接
    try:
        c,addr = s.accept()
        print("Connect from",addr)
    except KeyboardInterrupt as e:
        sys.exit("服务器退出")
    except Exception as e:
        print(e)
        continue

    # 创建线程
    t = Thread(target=handle,args=(c,))
    t.setDaemon(True)
    t.start()

ftp 文件服务器

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

IO并发

IO 分类

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

阻塞IO : 默认形态  效率低  实现简单

非阻塞IO : 将阻塞行为变为非阻塞 超时检测

  setblocking()
  settimeout()

阻塞IO

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

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

3.阻塞情况:

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

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

#### 非阻塞IO

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

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

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

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

IO多路复用

  1. 定义

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

  1. 具体方案

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

3.对比

select :
优点: 跨平台性好(win linux unix)
缺点: 效率一般,最多监控1024个IO

poll :
优点: 监控IO数量没有限制
缺点: 跨平台一般(linux unix),效率一般

epoll
优点: 监控IO没有限制,效率高
缺点: 跨平台性差 (linux)

 select,poll:
      * 每次监控IO都需要将应用层关注的IO映射给内核处理
      * 内核感知有IO准备就绪告知应用层,应用层需要再次
      轮寻找到就绪的IO

    epoll:
      * 直接将关注的IO放到内核空间进行监控,不必每次都从
      应用层映射给内容
      * 内核会直接提供就绪的IO给应用层处理
      * epoll提供了 EPOLLET的边缘触发方法,可以忽略不想
      处理的IO
select 方法
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多路复用消耗资源较少,效率较高


代码示例
"""
select tcp 服务
重点代码

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

from socket import *
from select import select

# 创建监听套接字作为关注IO
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(('0.0.0.0',8888))
s.listen(3)

# 设置关注列表
rlist = [s] # 等待客户端连接
wlist = []
xlist = []

# 监控IO发生
while True:
    rs,ws,xs = select(rlist,wlist,xlist)
    for r in rs:
        if r is s:
            # 有客户端连接
            c,addr = r.accept()
            print("Connect from",addr)
            rlist.append(c) # 连接对象加入监控
        else:
            data = r.recv(1024).decode()
            if not data:
                rlist.remove(r) # 取消对它关注
                r.close()
                continue
            print(data)
            # r.send(b'OK')
            wlist.append(r)

    for w in ws:
        w.send(b'OK')
        wlist.remove(w) # 从写监控中移除
扩展@ 位运算

定义 : 将整数转换为二进制,按二进制位进行运算

运算符号:

  		&  按位与
  		|  按位或
  		^  按位异或
  		<< 左移
  		>> 右移
e.g.  14 --> 01110
      19 --> 10011

14 & 19 = 00010 = 200
14 | 19 = 11111 = 3111
14 ^ 19 = 11101 = 29 相同为0不同为1
14 << 2 = 111000 = 56 向左移动低位补0
14 >> 2 = 11 = 3  向右移动去掉低位

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

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

poll_server 步骤

【1】 创建套接字
【2】 将套接字register
【3】 创建查找字典,并维护
【4】 循环监控IO发生
【5】 处理发生的IO
示例代码
"""
poll  方法实现IO多路服用
重点代码

【1】 创建套接字
【2】 将套接字register
【3】 创建查找字典,并维护
【4】 循环监控IO发生
【5】 处理发生的IO
"""

from socket import *
from select import *

# 创建套接字作为关注IO
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(('0.0.0.0',8888))
s.listen(3)

# 创建poll对象
p = poll()

# 建立查找字典,通过IO对象的fileno找到对象
# 字典内容与关注IO保持一直{fileno:io_obj}
fdmap = {s.fileno():s}

# 关注s
p.register(s,POLLIN|POLLERR)

# 循环监控IO的发生
while True:
    events = p.poll()       #return (fd,event值)
    print(events)
    for fd,event in events:
        if fd == s.fileno():
            c,addr = fdmap[fd].accept()
            print("Connect from",addr)
            # 添加新的关注对象,同时维护字典
            p.register(c,POLLIN)
            fdmap[c.fileno()] = c
        elif event & POLLIN:
            data = fdmap[fd].recv(1024).decode()
            if not data:
                # 客户端退出
                p.unregister(fd) # 取消关注
                fdmap[fd].close()
                del fdmap[fd]
                continue
            print(data)
            p.register(fd,POLLOUT)
            # fdmap[fd].send(b'OK')
        elif event & POLLOUT:
            fdmap[fd].send(b'OK')
            p.register(fd, POLLIN)
epoll方法
  1. 使用方法 : 基本与poll相同

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

    • epoll 效率比select poll要高
    • epoll 监控IO数量比select要多
    • epoll 的触发方式比poll要多 (EPOLLET边缘触发)
示例代码
"""
epoll  方法实现IO多路服用
重点代码

【1】 创建套接字
【2】 将套接字register
【3】 创建查找字典,并维护
【4】 循环监控IO发生
【5】 处理发生的IO
"""

from socket import *
from select import *

# 创建套接字作为关注IO
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(('127.0.0.1',8888))
s.listen(3)

# 创建epoll对象
ep = epoll()

# 建立查找字典,通过IO对象的fileno找到对象
# 字典内容与关注IO保持一直{fileno:io_obj}
fdmap = {s.fileno():s}

# 关注s
ep.register(s,EPOLLIN|EPOLLERR)

# 循环监控IO的发生
while True:
    events = ep.poll()
    print("你有新的IO需要处理哦:",events)
    for fd,event in events:
        if fd == s.fileno():
            c,addr = fdmap[fd].accept()
            print("Connect from",addr)
            # 添加新的关注对象,同时维护字典
            ep.register(c,EPOLLIN|EPOLLET) # 边缘触发
            fdmap[c.fileno()] = c
        # elif event & EPOLLIN:
        #     data = fdmap[fd].recv(1024).decode()
        #     if not data:
        #         # 客户端退出
        #         ep.unregister(fd) # 取消关注
        #         fdmap[fd].close()
        #         del fdmap[fd]
        #         continue
        #     print(data)
        #     ep.unregister(fd)
        #     ep.register(fd, POLLOUT)
        # elif event & POLLOUT:
        #     fdmap[fd].send(b'OK')
        #     ep.unregister(fd)
        #     ep.register(fd, POLLIN)

协程技术

基础概念

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

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

  3. 协程优缺点

优点

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

缺点

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


扩展延伸@标准库协程的实现

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


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

  • 函数

greenlet.greenlet(func)
功能:创建协程对象
参数:协程函数

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

  • 函数

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】 注意:脚本运行函数需要在对应模块导入前执行

HTTPServer v2.0

  1. 主要功能 :
    【1】 接收客户端(浏览器)请求
    【2】 解析客户端发送的请求
    【3】 根据请求组织数据内容
    【4】 将数据内容形成http响应格式返回给浏览器

  2. 升级点 :
    【1】 采用IO并发,可以满足多个客户端同时发起请求情况
    【2】 做基本的请求解析,根据具体请求返回具体内容,同时满足客户端简单的非网页请求情况

    【3】 通过类接口形式进行功能封装

示例代码
"""
httpserver 2.0
env: python3.6
io多路复用 http练习
"""
from socket import *
from select import select


class HTTPServer:
    def __init__(self, host='0.0.0.0', port=80, dir=None):
        self.host = host
        self.port = port
        self.dir = dir
        self.address = (host, port)
        # select 监控列表
        self.rlist = []
        self.wlist = []
        self.xlist = []
        self.create_socket()
        self.bind()

    # 创建套接字
    def create_socket(self):
        self.sockfd = socket()
        self.sockfd.setsockopt(SOL_SOCKET,
                               SO_REUSEADDR,
                               1)

    # 绑定地址
    def bind(self):
        self.sockfd.bind(self.address)

    def serve_forever(self):
        self.sockfd.listen(3)
        print("Listen the port %d" % self.port)
        # 设置关注的IO
        self.rlist.append(self.sockfd)
        while True:
            rs, ws, xs = select(self.rlist,
                                self.wlist,
                                self.xlist)
            for r in rs:
                if r is self.sockfd:
                    c, addr = r.accept()
                    self.rlist.append(c)
                else:
                    # 有客户端发请求
                    self.handle(r)

    # 处理客户端请求
    def handle(self, connfd):
        # 接收http请求
        request = connfd.recv(4096)
        # 客户端断开处理
        if not request:
            self.rlist.remove(connfd)
            connfd.close()
            return

        # 提取请求内容
        request_line = request.splitlines()[0]
        info = request_line.decode().split(' ')[1]
        print(info)

        # 根据info情况分类
        if info == '/' or info[-5:] == '.html':
            self.get_html(connfd, info)
        else:
            self.get_data(connfd, info)

    def get_html(self, connfd, info):
        if info == '/':
            # 主页
            filename = self.dir + "/index.html"
        else:
            # 其他网页
            filename = self.dir + info
        try:
            f = open(filename)
        except Exception:
            # 没有网页
            response = "HTTP/1.1 404 Not Found\r\n"
            response += 'Content-Type:text/html\r\n'
            response += '\r\n'
            response += "<h1>Sorry</h1>"
        else:
            # 有网页
            response = "HTTP/1.1 200 OK\r\n"
            response += 'Content-Type:text/html\r\n'
            response += '\r\n'
            response += f.read()
        finally:
            connfd.send(response.encode())

    def get_data(self, connfd, info):
        f = open(self.dir + '/timg.jpeg', 'rb')
        response = "HTTP/1.1 200 OK\r\n"
        response += "Content-Type:image/jpeg\r\n"
        response += '\r\n'
        response = response.encode() + f.read()
        connfd.send(response)


if __name__ == '__main__':
    # 用户自己提供的内容
    HOST = '0.0.0.0'
    PORT = 8000
    DIR = "./static"  # 网页目录

    http = HTTPServer(HOST, PORT, DIR)  # 实例化对象
    http.serve_forever()  # 启动服务

正则表达式re

动机

  1. 文本处理已经成为计算机常见工作之一

  2. 对文本内容的搜索,定位,提取是逻辑比较复杂的工作

  3. 为了快速方便的解决上述问题,产生了正则表达式技术

简介

  1. 定义

即文本的高级匹配模式,提供搜索,替换等功能。其本质是由一系列字符和特殊符号构成的字串,这个字串即正则表达式。

  1. 原理

通过普通字符和有特定含义的字符,来组成字符串,用以描述一定的字符串规则,比如:重复,位置等,来表达某类特定的字符串,进而匹配。

  1. 目标
  • 熟练掌握正则表达式元字符

  • 能够读懂常用正则表达式,编辑简单的正则规则

  • 能够熟练使用re模块操作正则表达式

元字符使用

普通字符

  • 匹配规则:每个普通字符匹配其对应的字符
e.g.
In : re.findall('ab',"abcdefabcd")
Out: ['ab', 'ab']
  • 注意事项:正则表达式在python中也可以匹配中文

或关系

  • 元字符: |

  • 匹配规则: 匹配 | 两侧任意的正则表达式即可

e.g.
In : re.findall('com|cn',"www.baidu.com/www.tmooc.cn")
Out: ['com', 'cn']

匹配单个字符

  • 元字符: .

  • 匹配规则:匹配除换行外的任意一个字符

e.g.
In : re.findall('张.丰',"张三丰,张四丰,张五丰")
Out: ['张三丰', '张四丰', '张五丰']

匹配字符集

  • 元字符: [字符集]

  • 匹配规则: 匹配字符集中的任意一个字符

  • 表达形式:

[abc#!好] 表示 [] 中的任意一个字符
[0-9],[a-z],[A-Z] 表示区间内的任意一个字符
[_#?0-9a-z] 混合书写,一般区间表达写在后面

e.g.
In : re.findall('[aeiou]',"How are you!")
Out: ['o', 'a', 'e', 'o', 'u']

匹配字符集反集

  • 元字符:[^字符集]

  • 匹配规则:匹配除了字符集以外的任意一个字符

e.g.
In : re.findall('[^0-9]',"Use 007 port")
Out: ['U', 's', 'e', ' ', ' ', 'p', 'o', 'r', 't']

匹配字符串开始位置

  • 元字符: ^

  • 匹配规则:匹配目标字符串的开头位置

e.g.
In : re.findall('^Jame',"Jame,hello")
Out: ['Jame']

匹配字符串的结束位置

  • 元字符: $

  • 匹配规则: 匹配目标字符串的结尾位置

e.g.
In : re.findall('Jame$',"Hi,Jame")
Out: ['Jame']
  • 规则技巧: ^ 和 $必然出现在正则表达式的开头和结尾处。如果两者同时出现,则中间的部分必须匹配整个目标字符串的全部内容。

匹配字符重复

  • 元字符: *

  • 匹配规则:匹配前面的字符出现0次或多次

e.g.
In : re.findall('wo*',"wooooo~~w!")
Out: ['wooooo', 'w']

  • 元字符:+

  • 匹配规则: 匹配前面的字符出现1次或多次

e.g.
In : re.findall('[A-Z][a-z]+',"Hello World")
Out: ['Hello', 'World']

  • 元字符:?

  • 匹配规则: 匹配前面的字符出现0次或1次

e.g. 匹配整数
In [28]: re.findall('-?[0-9]+',"Jame,age:18, -26")
Out[28]: ['18', '-26']

  • 元字符:{n}

  • 匹配规则: 匹配前面的字符出现n次

e.g. 匹配手机号码
In : re.findall('1[0-9]{10}',"Jame:13886495728")
Out: ['13886495728']


  • 元字符:{m,n}

  • 匹配规则: 匹配前面的字符出现m-n次

e.g. 匹配qq号
In : re.findall('[1-9][0-9]{5,10}',"Baron:1259296994") 
Out: ['1259296994']

匹配任意(非)数字字符

  • 元字符: \d \D

  • 匹配规则:\d 匹配任意数字字符,\D 匹配任意非数字字符

e.g. 匹配端口
In : re.findall('\d{1,5}',"Mysql: 3306, http:80")
Out: ['3306', '80']

匹配任意(非)普通字符

  • 元字符: \w \W

  • 匹配规则: \w 匹配普通字符,\W 匹配非普通字符

  • 说明: 普通字符指数字,字母,下划线,汉字。

e.g.
In : re.findall('\w+',"server_port = 8888")
Out: ['server_port', '8888']

匹配任意(非)空字符

  • 元字符: \s \S

  • 匹配规则: \s 匹配空字符,\S 匹配非空字符

  • 说明:空字符指 空格 \r \n \t \v \f 字符

e.g.
In : re.findall('\w+\s+\w+',"hello    world")
Out: ['hello    world']

匹配开头结尾位置

  • 元字符: \A \Z

  • 匹配规则: \A 表示开头位置,\Z 表示结尾位置

匹配(非)单词的边界位置

  • 元字符: \b \B

  • 匹配规则: \b 表示单词边界,\B 表示非单词边界

  • 说明:单词边界指数字字母(汉字)下划线与其他字符的交界位置。

e.g.
In : re.findall(r'\bis\b',"This is a test.")
Out: ['is']

总结

类别元字符
匹配字符. […] [^…] \d \D \w \W \s \S
匹配重复* + ? {n} {m,n}
匹配位置^ $ \A \Z \b \B
其他| () \

正则表达式的转义

  1. 如果使用正则表达式匹配特殊字符则需要加 \ 表示转义。

特殊字符: . * + ? ^ $ [] () {} | \

e.g. 匹配特殊字符 . 时使用 \. 表示本身含义
In : re.findall('-?\d+\.?\d*',"123,-123,1.23,-1.23")
Out: ['123', '-123', '1.23', '-1.23']
  1. 在编程语言中,常使用原生字符串书写正则表达式避免多重转义的麻烦。
e.g.
python字符串  -->    正则    -->    目标字符串
"\\$\\d+"   解析为   \$\d+   匹配   "$100"

"\\$\\d+"  等同于  r"\$\d+"

贪婪模式和非贪婪模式

  1. 定义

贪婪模式: 默认情况下,匹配重复的元字符总是尽可能多的向后匹配内容。比如: * + ? {m,n}

非贪婪模式(懒惰模式): 让匹配重复的元字符尽可能少的向后匹配内容。

  1. 贪婪模式转换为非贪婪模式
  • 在匹配重复元字符后加 ‘?’ 号即可
*  :  *?
+  :  +?
?  :  ??
{m,n} : {m,n}?
e.g.
In : re.findall(r'\(.+?\)',"(abcd)efgh(higk)")
Out: ['(abcd)', '(higk)']

正则表达式分组

  1. 定义

在正则表达式中,以()建立正则表达式的内部分组,子组是正则表达式的一部分,可以作为内部整体操作对象。

  1. 作用
  • 可以被作为整体操作,改变元字符的操作对象
e.g.  改变 +号 重复的对象
In : re.search(r'(ab)+',"ababababab").group()
Out: 'ababababab'

e.g. 改变 |号 操作对象
In : re.search(r'(王|李)\w{1,3}',"王者荣耀").group()
Out: '王者荣耀'
  • 可以通过编程语言某些接口获取匹配内容中,子组对应的内容部分
e.g. 获取url协议类型
re.search(r'(https|http|ftp|file)://\S+',"https://www.baidu.com").group(1)

  1. 捕获组

可以给正则表达式的子组起一个名字,表达该子组的意义。这种有名称的子组即为捕获组。

格式:(?P<name>pattern)

e.g. 给子组命名为 "pig"
In : re.search(r'(?P<pig>ab)+',"ababababab").group()
Out: 'ababababab'
In : re.search(r'(?P<pig>ab)+',"ababababab").group('pig')
Out: 'ab'
  1. 注意事项
  • 一个正则表达式中可以包含多个子组
  • 子组可以嵌套,但是不要重叠或者嵌套结构复杂
  • 子组序列号一般从外到内,从左到右计数

在这里插入图片描述

正则表达式匹配原则

  1. 正确性,能够正确的匹配出目标字符串.
  2. 排他性,除了目标字符串之外尽可能少的匹配其他内容.
  3. 全面性,尽可能考虑到目标字符串的所有情况,不遗漏.

Python re模块使用


 regex = compile(pattern,flags = 0)
 功能: 生产正则表达式对象
 参数: pattern  正则表达式
      flags  功能标志位,扩展正则表达式的匹配
 返回值: 正则表达式对象

 re.findall(pattern,string,flags = 0)
 功能: 根据正则表达式匹配目标字符串内容
 参数: pattern  正则表达式
      string 目标字符串
      flags  功能标志位,扩展正则表达式的匹配
 返回值: 匹配到的内容列表,如果正则表达式有子组则只能获取到子组对应的内容

 regex.findall(string,pos,endpos)
 功能: 根据正则表达式匹配目标字符串内容
 参数: string 目标字符串
      pos 截取目标字符串的开始匹配位置
      endpos 截取目标字符串的结束匹配位置
 返回值: 匹配到的内容列表,如果正则表达式有子组则只能获取到子组对应的内容

re.split(pattern,string,flags = 0)
功能: 使用正则表达式匹配内容,切割目标字符串
参数: pattern  正则表达式
     string 目标字符串
     flags  功能标志位,扩展正则表达式的匹配
返回值: 切割后的内容列表

 re.sub(pattern,replace,string,count,flags = 0)
 功能: 使用一个字符串替换正则表达式匹配到的内容
 参数: pattern  正则表达式
      replace  替换的字符串
      string 目标字符串
      max  最多替换几处,默认替换全部
      flags  功能标志位,扩展正则表达式的匹配
 返回值: 替换后的字符串

 re.subn(pattern,replace,string,count,flags = 0)
 功能: 使用一个字符串替换正则表达式匹配到的内容
 参数: pattern  正则表达式
      replace  替换的字符串
      string 目标字符串
      max  最多替换几处,默认替换全部
      flags  功能标志位,扩展正则表达式的匹配
 返回值: 替换后的字符串和替换了几处
示例代码
"""
regex.py  re模块
"""

import re

s = "Alex:1997,Sunny:1996" # 目标字符串
pattern = r"(\w+):(\d+)" # 正则表达式

# re模块调用
l = re.findall(pattern,s)
print(l)

# 正则对象调用
regex = re.compile(pattern)
l = regex.findall(s,0,10)
print(l)


# 正则表达式内容切割字符串
l = re.split(r',',s)
print(l)


# 替换目标字符串
s = re.subn(r':','--',s, 10)
print(s)

 re.finditer(pattern,string,flags = 0)
 功能: 根据正则表达式匹配目标字符串内容
 参数: pattern  正则表达式
      string 目标字符串
      flags  功能标志位,扩展正则表达式的匹配
 返回值: 匹配结果的迭代器

re.fullmatch(pattern,string,flags=0)
功能:完全匹配某个目标字符串
参数:pattern 正则
	string  目标字符串
返回值:匹配内容match object

re.match(pattern,string,flags=0)
功能:匹配某个目标字符串开始位置
参数:pattern 正则
	string  目标字符串
返回值:匹配内容match object

re.search(pattern,string,flags=0)
功能:匹配目标字符串第一个符合内容
参数:pattern 正则
	string  目标字符串
返回值:匹配内容match object

compile对象属性
【1】 pattern : 正则表达式
【2】 groups : 子组数量
【3】 groupindex : 捕获组名与组序号的字典


match对象的属性方法

  1. 属性变量
  • pos 匹配的目标字符串开始位置
  • endpos 匹配的目标字符串结束位置
  • re 正则表达式
  • string 目标字符串
  • lastgroup 最后一组的名称
  • lastindex 最后一组的序号
  1. 属性方法
  • span() 获取匹配内容的起止位置

  • start() 获取匹配内容的开始位置

  • end() 获取匹配内容的结束位置

  • groupdict() 获取捕获组字典,组名为键,对应内容为值

  • groups() 获取子组对应内容

  • group(n = 0)

    功能:获取match对象匹配内容
    参数:默认为0表示获取整个match对象内容,如果是序列号或者组名则表示获取对应子组内容
    返回值:匹配字符串

在这里插入图片描述

flags参数扩展

1. 使用函数:re模块调用的匹配函数。如:re.compile,re.findall,re.search....

2. 作用:扩展丰富正则表达式的匹配功能

3. 常用flag

A == ASCII 元字符只能匹配ascii码

I == IGNORECASE 匹配忽略字母大小写

S == DOTALL 使 . 可以匹配换行

M == MULTILINE 使 ^ $可以匹配每一行的开头结尾位置

X == 可以添加注释

  1. 使用多个flag

    方法:使用按位或连接
    e.g. : flags = re.I | re.A

  2. """
    flags.py
    扩展标志位演示
    """
    
    import re
    
    s = """Hello
    北京
    """
    
    # 只能匹配ASCII编码
    # regex = re.compile(r'\w+',flags=re.A)
    
    # 忽略字母大小写
    # regex = re.compile(r'[a-z]+',flags=re.I)
    
    # 使. 可以匹配换行
    # regex = re.compile(r'.+',flags=re.S)
    
    # ^ $ 可以匹配每行开头结尾位置
    # regex = re.compile(r'Hello$',flags=re.M)
    pattern = '''hello # 匹配Hello
    \s #匹配换行
    \w+ # 匹配 北京
    '''
    regex = re.compile(pattern,flags=re.X | re.I)
    
    l = regex.findall(s)
    print(l)
    

©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页