【Python】Python 基础语法

Python基础语法全面解析

目录

      • 变量与数据类型
        • 变量
        • 数据类型
        • 声明变量
        • 变量命名规则
        • 多重赋值
        • 类型注释
        • Pylance[^4]
      • 运算符
        • 算术运算符
        • 比较运算符
        • 逻辑运算符
        • 位运算符[^8]
        • 成员运算符
        • 身份运算符
        • 拼接运算符
        • 重复运算符
        • 集合运算符
        • 浮点数精度问题
        • 隐式类型转换
        • 类型提升
        • 表达式与赋值运算符
        • 符号运算
        • 运算后赋值运算符
        • 复合表达式
        • 海象运算符[^19]
      • 流程控制
        • 代码语句
        • 执行顺序
        • 缩进和代码块
        • 关键字
        • 占位语句
        • 条件语句
        • 嵌套条件
        • 三元表达式
        • 模式匹配
        • 模式守卫
        • 循环语句
        • 循环控制语句
        • else 子句 - 循环语句
        • 异常处理
        • 异常处理语句
        • else 子句 - 异常处理语句
      • 函数与作用域
        • 代码复用与函数定义
        • 函数表达式
        • 函数参数
        • 作用域
        • 多参函数与多值函数
        • 默认参数
        • 不定长参数
        • 函数对象[^27]
        • Lambda 表达式[^28]
        • 递归函数
        • 函数闭包[^30]
        • 装饰器[^31]
        • 内置函数 - 基础的输入输出
        • 内置函数 - 强制类型转换
        • 内置函数 - 类型检查
      • 数据结构操作
        • 数据结构
        • 内置函数 - 数据结构构造函数
        • 索引访问
        • 切片
        • 内置函数 - 可迭代数据结构
        • 内置函数 - 索引绑定
        • 容器推导式
        • 生成器函数与生成器表达式
        • 数据管道
        • 内置函数 - 绑定和解包
        • 内置函数 - 映射和过滤
        • 内置函数 - 数据特征
        • 内置函数 - 布尔规约
        • 内置方法 - 排序

变量与数据类型

变量

变量是用于存储数据开发者自定义的名称,类似于一个地址,唯一标识程序中数据的位置。

数据类型

数据有多种类型:

数据类型数据
int1、0b1010、-2333等整数
float-0.8、2e-10等小数
complex1+2j、0.125j等复数
bool仅有真值True或False
str“Hello Python”、‘It is an apple’、’’‘I have a dream’’'等由双引号、单引号或三引号包裹的字符序列
list[1,2,3]、[“苹果”,“香蕉”]、[True,True,False]等用方括号[]定义的,用,隔开元素的元素序列
tuple(0,0,1)、(‘v_x’,‘v_y’)等用圆括号()定义的,用,隔开元素的元素序列,与list的区别在于不可增、不可删、不可改其元素序列中的元素
dict{‘李明’:23, ‘王红’:25, ‘张燕’:27}、{True:False, False:True}等用花括号{}定义的,用,隔开键值对的键值对集合
set{‘x’,‘y’}、{0,1,2,6}等用花括号{}定义的,用,隔开元素的元素集合
frozenset同set,但是不可增、不可删、不可改其元素集合中的元素
range用range()函数定义的数据类型的数据
bytes如b’hello’、b’\x01\x02’等不可变的二进制数据
bytearray同bytes,但是可增、可删、可改其元素集合中的元素,由bytes数据类型的数据通过bytearray()函数构造
NoneType仅有None一个值,表示空或缺失
声明变量

变量可存储不同类型的数据,而且可随时改变

x = 10 # 变量 x 存储 int 数据类型的数据 10
x = "Hello" # 变量 x 改存储 str 数据类型的数据 "Hello"
变量命名规则
  • 合法字符:字符/下划线开头,后面可接字母、数字、下划线
  • 大小写敏感:如 var 和 Var 被视为不同变量
  • 保留字禁止1:不能使用Python的保留字命名变量
  • 命名惯例2
    • 小写+下划线,如 user_name
    • 常量全大写,如 MAX_SIZE
多重赋值

变量允许两种简便的声明方式:

x, y, z = 1, 2, 3 # 并行赋值
### 相当于
# x = 1
# y = 2
# z = 3

a = b = c = 1 # 链式赋值
### 相当于
# a = 1
# b = 1
# c = 1
类型注释

如果希望提醒开发者,你希望某些变量只存储固定的数据类型的数据,你需要用到类型注释3

x:int # 直接声明,无需声明时赋值
x = 3 # 自然是可以赋值 int 数据类型的数据的
x = "example" # 但是赋值 str 数据类型的数据也不会报错,因为类型注释本质上是一种注释

建议对重要的变量添加类型注释。

Pylance4

如果你使用VS Code,你可以利用它丰富的插件来帮助你强制开发者给变量赋值类型注释所要求的数据类型,那就是使用Pylance5插件,安装后,如果代码有类型注释,出现类型错误问题时会直接实时高亮显示

运算符

算术运算符

符号 +-*///%** 用于算术运算,被称为算术运算符

  • bool 数据类型的数据在参与算术运算时,会转换为 int 数据类型的数据,其中True 转换成 1False 转换成 06
  • int 数据类型的数据在参与 / 运算时,会转换成 float 数据类型的数据;
  • int 数据类型的数据,在与 float 数据类型的数据一起进行算术运算时,会转换为 float 数据类型的数据;
  • float数据类型的数据在参与算术运算时,存在精度问题,如0.1+0.2的结果为0.30000000000000004;
  • int 数据类型的数据与与 float 数据类型的数据,在与 complex 数据类型的数据一起进行算术运算时,会转换为 complex 数据类型的数据;
  • complex 数据类型的数据在参与 / 运算或 ** 运算时,int 数据类型的实部虚部会转换为 float 数据类型的实部虚部
  • complex 数据类型的数据不能参与 // 运算或 % 运算;
  • 其余数据类型不能参与算术运算。
比较运算符

符号 ><>=<===!= 用于比较运算并返回bool数据类型的数据,被称为比较运算符

  • bool 数据类型的数据在参与比较运算时,会转换为 int 数据类型的数据,其中True 转换成 1False 转换成 06
  • int 数据类型的数据,在与 float 数据类型的数据一起进行比较运算时,会转换为 float 数据类型的数据;
  • int 数据类型的数据与与 float 数据类型的数据,在与 complex 数据类型的数据一起进行比较运算时,会转换为 complex 数据类型的数据;
  • complex 数据类型的数据只能参与**==** 运算或 != 运算。
  • 其余数据类型的数据只能参与**==** 运算或 != 运算。
逻辑运算符

符号 andornot 用于逻辑运算,被称为比较运算符

  • bool 数据类型的数据在参与逻辑运算式,会转换为 bool 数据类型的数据,其中00.00j""[](){}set()range(0) 在参与逻辑运算时,会转换为 False,其余会转换为 True
  • a and b,若a可转换为False,结果为a7
  • a or b,若a可转换为True,结果为a7
  • not a,若a可转换为True,结果为False,否则True7
位运算符8

符号 &|^~<<>> 用于位运算,被称为位运算符9

  • bool6 数据类型的数据与非二进制数int 数据类型的数据在参与位运算时作为 &|^~ 运算的 操作数,会转换为 二进制数int 数据类型的数据10
  • bool6 数据类型的数据与非二进制数int 数据类型的数据在参与位运算时作为 <<>> 运算的 左操作数,会转换为 二进制数int 数据类型的数据10
  • <<>> 运算的右操作数必须是 int 数据类型的数据;
  • 其余数据类型的数据不能参与位运算。
成员运算符

符号 innot in 用于检查元素是否在容器中,被称为成员运算符

  • 一个str 数据类型的数据是另一个 str 数据类型的数据的连续部分11,参与 in 运算时返回 True12 ,不是则返回 False
  • 一个str 数据类型的数据是另一个 str 数据类型的数据的连续部分11,参与 not in 运算时返回 False13 ,不是则返回 True
  • 一个不可变元素是另一个由不可变元素 构成的listtuplesetfrozensetrange数据类型数据的成员,参与 in 运算时返回 True,不是则返回 False
  • 一个不可变元素是另一个由不可变元素 构成的 listtuplesetfrozensetrange数据类型数据的成员,参与 not in 运算时返回 False,不是则返回 True
  • 一个不可变元素是另一个 dict 数据类型数据的,参与 in 运算时返回 True,不是则返回 False
  • 一个不可变元素是另一个 dict 数据类型数据的,参与 not in 运算时返回 False,不是则返回 True
  • 一个bytesbytearray 数据类型的数据是另一个 bytesbytearray 数据类型的数据的连续部分11,参与 in 运算时返回 True14 ,不是则返回 False
  • 一个bytesbytearray 数据类型的数据是另一个 bytesbytearray 数据类型的数据的连续部分11,参与 not in 运算时返回 False15 ,不是则返回 True
身份运算符

符号 isis not 用于判断两个数据的存储地址是否相等,被称为 身份运算符

  • 小整数池(-5 到 256)中的 int 数据类型的数据会被Python 缓存,两个变量赋值相同的这类数据,地址是相同的,与该数据也是地址相同的;
  • 某些 str 数据类型的数据可能会有字符串驻留的现象,Python会自动缓存满足某种 标准16str 数据类型的数据,两个变量赋值这类数据,地址是相同的,与该数据也是地址相同的;
  • Python会自动缓存满足某种 标准17 的某些不可变数据,两个变量赋值这种数据,地址是相同的,与该数据也是地址相同的;
  • 使用链式赋值的几个变量,地址是 相同的;
  • 被Python以某种 机制18 缓存 的数据,与赋值该数据的变量是地址相同的,与本身也是地址相同的;
  • 两个赋值TrueFalseNone的变量,地址是相同的,与该数据也是地址相同的;
  • 若两个变量地址是相同的,参与 is 运算的结果是 True,否则是 False
  • 若两个变量地址是相同的,参与 is not 运算的结果是 False,否则是 True
拼接运算符

符号 + 用于拼接元素序列,被称为拼接运算符

  • 只有strlisttuplebytesbytearray 数据类型的数据才参与拼接;
  • bytes 数据类型的数据与,bytearray 数据类型的数据参与拼接时,会转换为 bytearray 数据类型的数据;
  • 按照拼接顺序首尾相接
  • 拼接的结果存储在新的 地址 中,不与任何参与拼接的数据的地址相同。
重复运算符

符号 * 用于重复元素序列,被称为 重复运算符

  • 只有strlisttuplebytesbytearray 数据类型的数据才能充当左操作数
  • 只有正的 int 数据类型的数据才能充当 右操作数
  • 左操作数按照右操作数的大小重复自身序列;
  • 重复的结果存储在新的 地址 中,与被重复数据的地址相同;
集合运算符

符号 &|-^ 用于 集合运算,被称为 集合运算符:只有 setfrozenset 才能参与集合运算。

浮点数精度问题

计算机使用 二进制数 存储 float 数据类型的数据,而大多数 十进制小数 在二进制中是 无限循环小数

Python默认使用的 IEEE 754 双精度浮点数19,使用64位存储 float 数据类型的数据,即 1 位符号位,11 位指数位,52 位有效数字位,二进制无法精确表示无限循环小数,只能截断或舍弃,导致精度丢失。

0.1 + 0.2 == 0.3 #结果为 False
0.1 + 0.2 #结果为 0.30000000000000004

1e16 + 1.0 == 1e16 #结果为 True

因此最好不要 直接比较 float 数据类型的数据。20

隐式类型转换

在Python中,隐式类型转换是指程序在 运行时 自动 将一种数据类型的数据 转换成 另一种数据类型的数据,以上存在许多例子,不再赘述。

算术运算比较运算时,转换次序为 boolintfloatcomplex 目的是为了 保证运算的精度

拼接运算时,转换次序为 bytesbytearray

逻辑运算位运算 时,没有转换次序,见逻辑运算符位运算

类型提升

有些运算符会 提升 某些数据类型的数据的数据类型,即使并没有遇到触发隐式类型转换的另一个操作数,只有 / 运算中有 int 数据类型的数据或 complex 数据类型的数据的 实部虚部int 数据类型的数据时,才会有这种情况。

表达式与赋值运算符

= 就是Python的赋值运算符,用于赋值变量赋值 是唯一没有运算结果的运算,或者说它的运算结果是 None

操作数运算符 的结合被称为 表达式

可以把表达式当做数据赋值给变量。

x = 1 + 2 #表达式为 1 + 2
符号运算

变量可以当做操作数参与运算,此时它的值是它存储的值,它的数据类型是它的值的数据类型

x = 1
y = x + 2

其中,将一个变量赋值给另一个变量,两个变量的地址是相同的;

x = 1
y = x
y is x # 结果是 True
运算后赋值运算符

表达式当做数据赋值给变量可以被简写,以下是简写方式:

x = 1
x += 2 # 加后赋值运算符
### 等价于:
# x = 1
# x = x + 2

y = 1
y -= 2 # 减后赋值运算符
### 等价于:
# y = 1
# y = y + 2

z = 1
z *= 2 # 乘后赋值运算符
### 等价于:
# z = 1
# z = z + 2

w = 1
w /= 2 # 除后赋值运算符
### 等价于:
# w = 1
# w = w + 2

# 以此类推

所有算术运算符拼接运算符重复运算符集合运算符 都有对应的 运算后赋值运算符位运算符 中只有 ~ 运算符 没有 对应的 运算后赋值运算符&|^<<>> 都有对应的 运算后赋值运算符,剩下来的所有运算符都没有对应的运算后赋值运算符

复合表达式

表达式 也可以被视作 操作数 参与构建表达式,此时它的运算结果的值就是它的值,它的运算结果的数据类型就是它的数据类型,它们的运算被称为 复合运算 ,它们与 运算符 的结合被称为 复合表达式

x = (1 + 2) * 3
### 等价于
# x = 1 + 2
# x *= 3
海象运算符21

Python 3.8+ 以上版本引入了 海象运算符,是一种唯一同时具有赋值运算结果能力的运算符,严格来说是一种赋值运算符
:=海象运算符,有这个名称的原因在于它长得像一只躺倒的海象。

y = (x := 1) + 2 
### 海象运算
# x 此时存储 int 数据类型的数据 1
# 而 x := 1 运算的结果与 x 存储的数据一样
# 代码等价于
# x = 1
# y = x + 2

流程控制

代码语句

代码语句 是程序在 运行 过程中,被 处理 的代码22,明确告诉解释器,它现在应该做什么。

执行顺序

代码在程序运行过程中,处理代码的顺序被称为 执行顺序,Python是严格按照 书写顺序 严格处理代码的,因此Python的 执行顺序从上到下 的。

缩进和代码块

在Python中,缩进代码块是互相定义的,因此我们放在一起讲。

缩进23 标记 代码块开始结束代码块 是一组按照 缩进 组织在一起的 代码语句,它们作为一个 整体 被处理。

    x = 1
    x += 2 # 一个代码块内的代码语句
x = 1 # 另一个代码块内的代码语句

注意,Python默认按书写顺序处理它遇到的第一个代码块,因此缩进不一致的代码不会被处理,还会抛出 IndentationError

关键字

关键字 是Python的 保留字,它类似自然语言中的 助词,被用来 辅助构建 Python代码的 语法结构

占位语句

pass 是Python的 关键字,也是Python的代码语句,被称为占位语句,表示程序处理这条语句时不应该做任何操作

pass
条件语句

ifelifelse 是Python的 关键字ifelif表达式: ,或者 else: 的结合被称为 条件语句

相同缩进的 条件语句,从 if 开始,中间要求 不存在 同缩进的 非条件语句 或其他以 if 开头的条件语句,则为同一族 语句序列。其中,当中若存在 else,则必定唯一且为语句序列的最后一条语句

if ((x := 3) ** 2) % 3 == 0:
    pass
elif (x ** 2) % 3 == 1:
    x += 2
else:
    x += 1
### 这里的 
# if ((x := 3) ** 2) % 3 == 0:
# elif (x ** 2) % 3 == 1:
# 和 else:
# 是同一族的语句序列
# 而以下的:
# if ((x := 3) ** 2) % 3 == 0:
# 和 elif (x ** 2) % 3 == 1:
# 是同一族的语句序列
# else: 不属于任何一族语句序列

if ((x := 3) ** 2) % 3 == 0:
    pass
elif (x ** 2) % 3 == 1:
    x += 2
x += 1
else:
    pass
# 

若存在不属于任何一族语句序列的 条件语句,那么这条条件语句是 不合法 的。

条件语句 可改变程序的 执行顺序,解释器遇到一条if开头的条件语句,则从与这条条件语句同一族条件语句序列开始开始检查,当遇到第一个表达式的运算结果可转换为 True时,执行该语句下的第一个代码块,如果找不到但是有else 开始的条件语句,则执行它下面的第一个代码块。执行完则跳出该族序列直到遇到第一条同缩进代码语句

if ((x := 3) ** 2) % 3 == 0:
    pass 
    # 执行 if ((x := 3) ** 2) % 3 == 0:
    # 之后的第一个代码块,也就是这里
elif (x ** 2) % 3 == 1:
    x += 2
else:
    x += 1
# 执行完则跳到这里
嵌套条件

条件语句块 可以 多层嵌套,这是由语句块的定义所决定的。

if (x := 25) >= 18:
    if y := True:
        pass
    else:
        pass
else:
    pass
三元表达式

表达式1if表达式2else表达式3 的结合被称为 三元表达式三元表达式 不改变执行顺序,它有运算结果,是表达式,若表达式2的运算结果可转换为True,则三元表达式的运算结果为表达式1的运算结果,否则为表达式3的运算结果。

x = -5
y = '正数' if x > 0 else '负数'
模式匹配

matchcase是Python的关键字match操作数:,或者case操作数: 构成 match-case语句

case开头的match-case语句必须处在match开头的match-case语句下的第一个语句块之中。match开头的match-case语句下的第一个语句块中只能有case开头的match-case语句及其语句块

match x := 3:
    case 1:
        match r := 1:
            case x:
                pass
    case 2:
        if (y := 6) > x:
            pass
    case 3:
        x += 3 if x == 3 else 0
    ### 这说明了
    # 它并不关心
    # case 开头的 match-case 语句下的
    # 语句块里有什么内容

match-case 语句 会改变执行顺序,当解释器遇到 match 开头的 match-case 语句 时,它会逐个扫描它下面第一个语句块的所有case开头的match-case 语句

若一条case开头的match-case 语句中的操作数的值与match 开头的 match-case 语句中的操作数的值相同,则进入它下面的第一个语句块并执行。

随后跳出match 开头的 match-case 语句下第一个语句块,并继续按顺序执行直到遇到与match 开头的 match-case 语句同缩进的代码语句。

match value := 2:
    case 1:
        value += 2
    case 2:
        value += 1
        # 进入这里
# 然后到这

如果一个也 匹配不到,可以选择添加一条默认匹配语句case_:的结合构成一条match-case语句,表示 如果一切都不匹配,则匹配这条

match a := 1:
    case _:
        pass
        # 没有一个case开头的
        # match-case语句的操作数的值是 1
        # 进入这里
模式守卫

case操作数if表达式: 构成一条 match-case语句,表示若,操作数 匹配且 表达式 的运算结果可转换为 True,则匹配这条:

r, a = 2, 0
match pi := 3.14:
    case 3.14 if r == 1:
        a = pi * r ** 2
    case 3.14 if r == 2:
        a = pi * r ** 2
        # 匹配这里
循环语句

forin 是Python的 关键字for变量in可迭代数据类型的数据24: 构成一条 循环语句

for 开头的 循环语句 会改变 执行顺序in会逐一从可迭代数据类型的数据数据变量赋值,并每次来回进入它下面第一个代码块,直到取完跳出到与它下面同缩进的第一条语句:

t = 0
for s in 'apple':
    if s == 'p':
        t += 1
    # 一次取完 'a'、'p'、'p'、'l'和'e'
# 跳到这里

while 是Python的关键字while表达式: 的结合是一条 循环语句

while 开头的循环语句会改变执行顺序只要表达式的结果可转换为True重复进入执行它下面第一个代码块,直到表达式的结果可转换为 False跳出到与它下面同缩进的第一条语句:

p = 0
while p < 100: # 直到 p >= 100
    p += 1
    # 重复进入这里
# 然后到这
循环控制语句

循环语句 不总是需要执行完才能 退出,如果想要中途退出,可以使用 循环控制语句continuebreak是Python的关键字,也是循环控制语句,以下是用法:

y = 0
for x in [1, 2, 3, 4]:
    if x == 1:
        continue 
        ### 如果满足:
        # 则结束这次执行
        # 跳到下一次执行
    y += 1
    if x == 3:
        break # 如果满足,则跳出循环
else 子句 - 循环语句

你知道,循环语句重复操作 完了之后,我们可能有一些善后操作。原本你可以直接在跳出循环后执行那些操作,但是你的代码可能存在一些中断操作,提前在完成之前离开,这对你来说不是很妙

因此,Python引入了 else 子句else: 的结合是 else 子句,代表如果循环正常结束,则执行 else 子句下的第一个代码块:

sign = False
d = {'张三':23, '李四':24, '王五':25}
for k in d:
    if k == '赵六':
        break
else: # 没有赵六,正常结束
    d['赵六'] = 26 # 添加赵六

for k in d:
    if k == '赵六':
        break # 有赵六,跳出循环
else:
    d['赵六'] = 26 # 不会进入这里
异常处理

写Python脚本不可能总是不出错误,于是Python有很多异常类型,来告知你或者其他用户,操作中可能出现了预期之外的异常情况

异常类型含义
ValueError值不符合预期
TypeError操作应用到不适当数据类型的数据
IndexError索引超出序列范围
KeyError字典键不存在
FileNotFoundError文件未找到
ZeroDivisionError除以零
Exception反正就是发生了异常情况
异常处理语句

tryexceptfinally 是Python的关键字。try:,或者 except异常类型:,或者finally: 的结合被称为 异常处理语句

它们的行为 类似 match-case 语句25,但是有所不同。match 开始的 match-case 语句 需要 操作数,而 try 开始的 异常处理语句 不需要。

try 开始的 异常处理语句 蕴含着如果它下面的代码块有 异常 与 某个except 开始的 异常处理语句 中的异常类型 匹配,就执行这个 except 开始的 异常处理语句 下的第一个代码块。

try:
    x = 1 / 0 
    # 除于 0 会引发 ZeroDivisionError
except ZeroDivisionError:
    x = None # 不再报错,代码可以继续进行
# 然后跳到这里

它多了一条 finally 开始的 异常处理语句,这是 match-case 语句 所没有的,它是一种 收尾操作,无论有没有发生 异常情况,都要执行的代码块。

你可能会疑惑,为什么无论有没有发生异常都要执行finally代码块的内容。这是因为:你永远都不知道你的代码块里还有多少异常

异常是会阻塞代码执行的。

try:
    x = 1 / 0
except ZeroDivisionError:
    x = None
x = None 
# 假设你就是想让 x 是None,特别不讲理

这时候,如果代码还有其他错误,就会阻塞你的代码,不能继续执行26因此如果你无论如何都想执行某段操作,请放在finally开始的异常处理语句下的第一个代码块里。

else 子句 - 异常处理语句

你知道,如果你的代码没有异常,你肯定有想要在你的代码正常发挥功能后,添加一些必要的代码。但是事不遂人意,你的代码可能发生了异常,而你想添加的代码本应在你的代码发挥正常功能后继续执行,但是它因异常阻塞了。

于是你添加到了 finally 开始的异常处理语句下的第一个代码块里,却发现 更糟了。因为你的需求是,在你的代码正常发挥功能后,继续执行 接下来的代码,而不是 发生了异常后还要继续执行,因此你十分懊恼

Python很贴心的给你准备了 else子句,它和此前在条件语句循环语句中的形式一样,功能不尽相同:

try:
    x = 1 / 2
except ZeroDivisionError:
    x = None 
    # 除零错误,虽然理应不会发生
    # 进入过这里
    # 就不会再进入 else 子句下面的
    # 第一个代码块
else:
    y = 1 
    # 如果没有发生异常
# 顺利到这里
# 因除零错误到这里

函数与作用域

代码复用与函数定义

假设你写了一段 顶好的代码,你觉得你有必要彰显你的 顶好的才能,所以你到处使用你那 顶好的代码。但是,不出十日,你就会身心俱疲

你会想,果然天才就是要受磨难的,你来回重复敲你那 顶好的代码,但是敲那么多你都快了。于是你需要一个办法。

这就是函数的意义,代码复用是它的使命

假设你那 顶好的代码:

lst = [1, 2, 3, 4]
sm = 0 # 你要用的数据
for x in lst:
    sm += x
# 怎么看都不像是顶好的样子

怎么复用呢?让我们认识一下两个关键字defreturn

def变量(): 的结合被称为 函数签名return操作数 的组合被称为返回语句

按照以下要求:

  • 你要复用的代码放进 函数签名 下的第一个代码块;
  • 要用的 数据作为返回语句的操作数
def very_good():
    lst = [1, 2, 3, 4]
    sm = 0
    for x in lst:
        sm += x
    return sm
    # 看着很简单是吧

但是你如果没有要用的数据呢?你就只想让解释器运行一下你那 顶好的代码,只需要不写返回语句就可以。

函数表达式

兴致勃勃的使用函数,结果不到一天就气馁了,因为你不知道怎么让它在应该工作的地方工作。

那是因为你还不会调用函数

x:int
# 你想让 x 存储你那顶好的代码里面的要用到值

你现在有你觉得不好用的复用了你那顶好的代码的函数:

def very_good():
    lst = [1, 2, 3, 4]
    sm = 0
    for x in lst:
        sm += x
    return sm

你觉得程序运行到def very_good():没有任何操作直接跳过去了好像什么都没写一样

而且更关键的是,你不知道怎么拿要用的数据

请按以下步骤做:

x = very_good()
# 这个被称为函数表达式。

是的,问题很快解决了。

函数参数

没过几天,你 反应过来 了,这算什么 顶好的代码,这要求的数据完全是固定的,不符合你那天才一般的头脑,你要能求得不同数据的代码。

你现在有你那复用你觉得现在不符合你那天才一般的头脑的代码函数

def non_good():
    lst = [1, 2, 3, 4]
    sm = 0
    for x in lst:
        sm += x
    return sm

你觉得里面的 lst 可以是其他 list 数据类型的数据,因此你写了一堆不同 lst函数,然后你 又双叒叕身心俱疲 了。

注意看,我们只需要:

def non_good(lst):
    sm = 0
    for x in lst:
        sm += x
    return sm

这样,对任何不同的lst,我们都可以使用个锤子。你气愤地说了个锤子,说,“拿怎么取要用的数据呢?”

lst = [1, 1, 4, 5, 1, 4]
x:int 
### 你要函数给你要取的数据
# 然后存储在 x 中
# 你发现不知道怎么让函数知道你要用的lst

只需要:

x = non_good(lst)

函数签名上,括号内部的,称为形式参数,而函数表达式上,括号内部的,称为实际参数

心满意足,因为你是个天才,把它又改成very_good

作用域

现在你气鼓鼓了,慢着,你为什么要说?不管了,你现在发现,你无法理解一些事情。

count = 0
def increment():
    count += 1

你觉得这很有问题,你天才般的脑瓜很快就想到,你还要return干什么?这样不就可以实现自增操作吗?

increment()
increment()
increment()

但是现实总会恰当地给你破一盆冷水,这样做不行,如果这样可以的话,其他开发者就不知道接手到你的代码时,这个count已经被修改成什么神仙模样了。

Python用作用域的机制,防止你的代码被这种天才操作改得乱七八糟。

函数内部定义的变量,仅在函数内部有效,被称为 局部变量,函数外定义的变量,被称为 全局变量,不能直接被修改。

count = 0
def increment():
    count += 1
increment() # 会报 UnboundLocalError 错误
increment()
increment()

如果想要修改 count,有两种办法:

方法1传入参数并给 count 赋值

conut = 0
def increment(conut):
    return conut + 1
conut = increment(conut)
conut = increment(conut)
conut = increment(conut)

方法2global关键字:

conut = 0
def increment():
    global conut
    conut += 1
increment()
increment()
increment()

看到 方法2 你很高兴啊,想要到处用。

最好不要这么做!还是那句话,Python用作用域区分局部变量全局变量的目的就是为了防止你的代码被搞的乱七八糟,如果你到处用global,那作用域就没意义了。

多参函数与多值函数

你很 苦恼,不知道怎么办。原来,你有这么一个 需求

你的老师布置了一个题目,求 一个值另一个值,你觉得这太难了,于是你想用Python实现:

z = x + y

但你犯难了,你并不知道老师要给哪两个值,于是想用函数,因为你知道形参可以不知道具体值。

但是你只学会了用一个形参写函数。

不要着急,只需要:

def add(x, y):
    return x + y
z = add(x, y)

于是你豁然开朗,仿佛你是一个天才

以此类推,你可以写有多个形参的函数,这被称为 多参函数

def very_good(x, y, z, w):
    return (x + y) * (z - w)
v = very_good(x, y, z, w)

形式参数 书写的顺序就是实际参数 传入 的顺序

老师看到了,也很高兴,给你出了另一道题:求一个数与另一个数的和、差、积、商。

你现在又 苦巴巴 的了,求放过,老师不依不饶,让你必须做。

不用着急,只需要这样:

def calculator(x, y):
    return x + y, x - y, x * y, x / y
z, w, u, v = calculator(x, y)
### 这相当于:
# def add(x, y):
#     return x + y
# def sub(x, y):
#     return x - y
# def mul(x, y):
#     return x * y
# def div(x, y):
#     return x / y
# z, w, u, v = add(x, y), sub(x, y), mul(x, y), div(x, y)

以此类推,你可以写 返回语句多个的操作数 的函数,这被称为 多值函数

默认参数

你有一个 烦恼两顿炸鸡都不能消解:

def your_function(x, y, z, w):
    if w > 0:
        return x + y - z
    elif w == 0:
        return x - y + z
    else:
        return -x + y + z

你发现你的 w 似乎大部分,可能有90%的概率有 w 等于 0,你 实在懒得再写一个 w了,但是 w 也可能 大于小于 0,所以你怒吃了两顿炸鸡解闷,因为你不知道怎么办。

怎么办呢?办法是有的:

只需要:

def your_function(x, y, z, w = 0):
    if w > 0:
        return x + y - z
    elif w == 0:
        return x - y + z
    else:
        return -x + y + z

w此刻是默认的,如果不传入w的值,那么它默认在w的位置传入0:

your_variable = your_function(x, y, z)
### 等价于:
# your_variable = your_function(x, y, z, 0)

这被称为默认参数,而这种k = v的参数被称为 关键字参数

你觉得这 太棒了!再吃了一顿炸鸡,边吃边说这 太棒了,又写了一个函数。

def very_very_very_good(a=1, b=2, c):
    return a * b + a * c + b * c 
d = very_very_very_good(c)

看来你并没有懂。你愣住了,炸鸡掉了下来

默认参数 必须放在参数列表末尾,不然函数不知道你传入了什么:

def very_very_very_good(c, a=1, b=2):
    return a * b + a * c + b * c
d = very_very_very_good(c)

同时,如果需要给部分默认参数传值,应该指定是哪些默认参数,被称为关键字传入

d = very_very_very_good(c, b = 1)

你默默捡起炸鸡,继续大快朵颐起来,心理默记 再来一顿 以后不能这样了

不定长参数

你有一天觉得世界疯了,老师布置了一道题:给若干数字乘于2。

你问老师若干数字是几个数字,老师笑嘻嘻地说,“不告诉你。”

你觉得你完了,痛哭流涕,希望世界核平

当然,世界核平之前,来看看如何解决这道题。

你想到用 list 的数据类型传值:

def mul_2(lst):
    result = []
    for n in lst:
        result += [n * 2]
    return result

老师说,诶!是老师传数字,老师 不可能 给你 lst 的。

你懵了,祈祷核平的到来。

其实是有办法的,那就是 不定长参数

看代码:

def mul_2(*num):
    result = []
    for n in num:
        result += [n * 2]
    return result

lst_1 = mul_1(1, 1, 4, 5, 1, 4)
lst_2 = mul_2(1, 9, 1, 9, 8, 0, 1)

这样老师无论传入多少个数字无所谓了。

还有另一种不定长参数:

def mul_2(**kwarg):
    result = {}
    for k in kwarg:
        result[k] = kwarg[k] * 2
    return result
dit = mul_2('张三' = 24, '李四' = 21)

而这种方法可以传入若干关键字参数

如果你的代码中同时存在普通参数默认参数可变长的普通参数可变长的关键字参数,则先后排列顺序遵守普通参数默认参数可变长的普通参数可变长的关键字参数

函数对象27

Python的函数可以当做数据赋值给变量,函数是 Callable 数据类型的数据:

def f(x):
    return x + 1
def g(y):
    return y * 2

a = 1
x = f
y = g
z = x(y(a))

函数可以当做参数传入函数:

def f(x):
    return x + 1
def g(f, y):
    return f(y * 2)

a = 1
z = g(f, a)

函数可以当做返回语句的操作数:

def g(y):
    def f(x):
        return x + 1
    return f, y * 2

a = 1
x, b = g(a)
z = x(b)

函数可以存储在 listtupledictsetfrozenset等数据类型的数据中:

def f(x):
    return x + 1
def g(y):
    return y * 2

lst = [g, f]
z = 1
for func in lst:
    z = func(z)
Lambda 表达式28

Python的函数可以没有函数名,lambda 关键字可以声明 匿名函数

lambda参数、…、参数:表达式 等结合被称为 Lambda 表达式,它的运算结果是一个 匿名函数

f = lambda x: x + 1
g = lambda y: y * 2
a = 1
z = g(f(a))
递归函数

你现在感觉无所畏惧了,对函数的理解已经超然入世,你决心挑战你一直害怕的题目。

第 1 个数字是 1,第 2 个数字也是 1,假设从第三个数字开始,当前数字是前两个数字的和,那么第 n 个数字是几?

你的解法是:

def F(n):
    if 0 < n < 3:
        return 1
    elif n <= 0:
        return None
    a, b = 1, 1
    m = n
    while m - 2 > 0:
        a, b = b, a + b
        m -= 1
    return prev

解法有什么问题呢?没有,你很高兴

不过,还有一种解法。纳尼!还有你不知道的解法吗!

这就是接下来要讲的 递归函数,它的优点在于 简洁美观通俗易懂

def F(n):
    if 0 < n < 3:
        return 1
    elif n <= 0:
        return None
    return F(n - 2) + F(n - 1)

你看到递归函数,花都要谢了,似乎代码很少。

不过不用担心,递归函数也有它的缺点,那就是 低效29在你的解法解出值的时候,再强劲的计算机,递归版本的解法也已经让显卡冒烟了。

函数闭包30

作用域可以随着函数嵌套的层数加深而变得多层,而一个函数的作用域,在该函数的嵌套函数看来,是包含它的最小封闭作用域,称这个最小封闭作用域是它的函数闭包,包含它的函数称为它的外部函数

Python的函数能记住它的函数闭包局部变量的引用,那么在它当做返回语句操作数时,相当于携带着一个移动数据库

def g(r):
    a = 3.14 * r ** 2
    def f(h):
        v = a * h
        return v
    return f

r, h = 2, 1
v = g(r)(h)

函数g在事实上成为一个函数工厂,根据不同的r值,生成不同的函数g(r)

不过,当函数被外部函数返回时,函数闭包局部变量虽然可以读取,却不能修改31,即它是只读的。如果要修改,就必须使用 nonlocal 关键字。

def g(r):
    a = 3.14 * r ** 2
    def f(h):
        nonlocal a
        v = a * h
        a /= 4
        return v
    return f

r, h = 2, 1
a = g(r)
v_1 = a(h)
v_2 = a(h)
v_3 = a(h)

而且,函数闭包会导致外部函数局部变量无法得到及时释放,可能会造成内存泄露,需要谨慎使用。

装饰器31

你是否曾想过给函数做一些有趣的额外概念,即使它与函数本身的功能不是很相关

比如你想计算你调用了一个函数多少次了,我们可以利用函数闭包的性质做到这一点:

def count_func(func):
    count = 0
    def new_func(*arg, **kwarg):
        nonlocal count
        result = func(*arg, **kwarg)
        count += 1
        return result
    return new_func

def add(x, y):
    return x + y

add = count_func(add)
# 用新函数覆盖原函数

不过,有时候你不一定记得使用新函数覆盖原函数,甚至有时候原函数过长,你写完甚至都不知道原来想干什么

Python 为这一无伤大雅的漏洞打上了一个小补丁,这就是装饰器,一种专门将函数32当成任人打扮的小姑娘语法糖函数美容就找Python 装饰器

咳咳,玩笑开过分了。事实上,装饰器的主要功能是为了更方便地扩展函数功能,还是刚才的例子:

def count_func(func):
    count = 0
    def new_func(*arg, **kwarg):
        nonlocal count
        result = func(*arg, **kwarg)
        count += 1
        return result
    return new_func

@count_func
def add(x, y):
    return x + y
# 无需显式的用新函数覆盖原函数

装饰器也是可以像函数一样传递参数的,比如:

def decorator(model):
    def decorate_func(func):
        count = 0
        memory = []
        def new_func(*arg, **kwarg):
            nonlocal count
            nonlocal memory
            result = func(*arg, **kwarg)
            if model == 'count':
                count += 1
            elif model == 'memory':
                memory += [result]
            return result
        return new_func
    return decorate_func

@decorator('memory')
def add(x, y):
    return x + y
# 等价于 add = decorator('memory')(add)

装饰器也可以添加装饰器,实现代码界的俄罗斯套娃

def count_func(func):
    count = 0
    def new_func(*arg, **kwarg):
        nonlocal count
        result = func(*arg, **kwarg)
        count += 1
        return result
    return new_func

def memory_func(func):
    memory = []
    def new_func(*arg, **kwarg):
        nonlocal memory
        result = func(*arg, **kwarg)
        memory += [result]
        return result
    return new_func

@count_func
@memory_func
def add(x, y):
    return x + y
# 等价于 add = count_func(memory_func(add))
内置函数 - 基础的输入输出

控制台计算机 中用于与 用户 文本交互的界面,也称为 命令行界面终端,它允许用户依靠 文本命令操控 计算机。

Windows 原生的控制台是 命令提示符(CMD)PowerShell,而常见 IDE/编辑器33 的控制台则是 终端运行窗口

print() 函数可以在控制台 打印文本

print('Hello Console!')

运行程序:

python your_script.py
### 控制台将打印:
# Hello Console!

print() 还能打印多串文本:

print('apple', 'banana', 'orange')

运行程序:

python your_script.py
### 控制台将打印:
# apple banana orange

print()sep 参数设置 间隔 以隔开 多串 ** 文本,默认是sep = ’ '**,可以替换为其他间隔,比如:

print('www', 'python', 'org', sep = '.')

运行程序:

python your_script.py
### 控制台将打印
# www.python.org

input() 函数可以从控制台 获取文本

x = input()

运行程序:

python your_script.py
3

回车继续运行:

x = input()
# x 被赋值为 str 数据类型的数据 '3'

input() 函数可以在控制台打印提示文本,给用户提供信息

x = input('Please enter a positive integer')

运行程序:

python your_script.py
### 控制台将打印
# Please enter a positive integer
3

回车继续运行:

x = input('Please enter a positive integer')
# x 被赋值为 str 数据类型的数据 '3'
内置函数 - 强制类型转换

你可以使用 input() 函数要求用户输入一个 正整数

x = input('Please enter a positive integer')

运行程序:

python your_script.py
### 控制台将打印
# Please enter a positive integer
3

回车继续运行:

x = input('Please enter a positive integer')
# x 被赋值为 str 数据类型的数据 '3'

然而它不能像 int 数据类型的数据一样参与 算术运算,因为 input()返回语句操作数str 数据类型的数据。

Python 内置 了一系列 强制转换函数,你可以随时使用。

a = int('3') # 3
b = int(3.9) # 3,截断小数部分
c = int(True) # 1,True变成1,False变成0

d = float(3) # 3.0
e = float('3') # 3.0
f = float(True) # 1.0, True变成1.0,False变成0.0

g = str(3) # '3'
h = str(3.9) # '3.9'
i = str(True) # 'True'

j = bool(3) # True
k = bool(3.9) # True
l = bool('3') # True

注意

  • str() 可以把 任何数据强制转换str 数据类型的数据,因此例子过多,这里只展示部分;
  • bool() 可以把所有可转换成 False 的数据类型的数据强制转换成 False,否则强制转换成 True34
  • int()float() 在遇到 非数字字符,如 ‘abc’,会引发 ValueError 错误。

list()tuple()set() 函数会将一切可迭代的数据类型的数据24 强制转换listtupleset() 数据类型的数据。

dict() 函数会将 存储形如(key, value)的元素listtuple 数据类型的数据 强制转换dict 数据类型的数据。

内置函数 - 类型检查

Python中 所有数据 都有其 数据类型,Python内置了一些函数方便处理 数据类型

type() 函数可以告诉你数据数据类型

print(type(3))
print(type(3.9))
print(type('3'))
print(type(True))
print(type([3]))
print(type((1, 2)))
print(type({'张三': 25, '李四': 21}))
print(type({'apple', 'banana', 'orange'}))

运行程序:

python your_script.py
### 控制台将打印
# <class 'int'>
# <class 'float'>
# <class 'str'>
# <class 'bool'>
# <class 'list'>
# <class 'tuple'>
# <class 'dict'>
# <class 'set'>

isinstance() 函数可以检查数据数据类型是否是指定的数据类型

def f(x):
    if isinstance(x, int):
        return x ** 2
    elif isinstance(x, float):
        return int(x) ** 2
    elif isinstance(x, str):
        return None

a = 3
b = 3.9
c = '3'
print(f(a), f(b), f(c))

运行程序:

python your_script.py
### 控制台将打印
# 9 9 None

数据结构操作

数据结构

数据 需要被 组织存储管理 起来,用来 组织存储管理 的工具则被称为 数据结构

Python内置的 strlisttupledictsetfrozensetrangebytesbytearray 等数据类型的数据,既是数据,也是数据结构

按照有序性35可变性元素类型一致性36给上述数据结构分类:

数据结构有序性可变性元素类型一致性
str有序不可变字符
list有序可变元素类型可混合
tuple有序不可变元素类型可混合
dict无序可变值类型可混合
set无序可变元素类型可混合
frozenset无序不可变元素类型可混合
range有序不可变整数
bytes有序不可变字节
bytearray有序可变字节
内置函数 - 数据结构构造函数

rangebytearrayfrozenset 数据类型的数据是 不能直接创建的,Python 内置了 创建它们的函数。

range() 是Python的内置函数,可传入正的 int 数据类型的数据,返回语句的操作数的数据类型为 range

your_range = range(6)

print(isinstance(your_range, range))

运行程序:

python your_script.py
### 控制台将打印
# True

用正的 int 数据类型的数据,通过这种方式创建的 range 数据类型的数据,得到的是一个从 0 到该数据 -1 的整数序列:

your_range = range(6)

for x in your_range:
    print(x)

运行程序:

python your_script.py
### 控制台将打印
# 0
# 1
# 2
# 3
# 4
# 5

可以从 range() 函数得到从一个 int 数据结构的数据到另一个 int 数据结构的数据 -1 的整数序列37,同样也是 range 数据类型的:

your_range = range(1, 4)

for x in your_range:
    print(x)

print('|------------')

print(isinstance(your_range, range))

运行程序:

python your_script.py
### 控制台将打印
# 1
# 2
# 3
# |------------
# True

在此基础上,可以获取固定 int 数据类型的数据的步长的整数序列,同样也是 range数据类型的:

for x in range(1,4,2):
    print(x)

print('|------------')

print(isinstance(range(1,4,2), range))

运行程序:

python your_script.py
### 控制台将打印
# 1
# 3
# |------------
# True

bytearray() 是Python的内置函数,可传入正的 int 数据类型的数据,返回语句的操作数的数据类型为 bytearray

print(isinstance(bytearray(6), bytearray))

运行程序:

python your_script.py
### 控制台将打印
# True

用正的 int 数据类型的数据,通过这种方法获取的是一个长度为正的 int 数据类型的数据的全 038 的字节序列:

for x in bytearray(6):
    print(x)

运行程序:

python your_script.py
### 控制台将打印
# 0
# 0
# 0
# 0
# 0
# 0

可以传入 str 数据类型的数据,此时需要设置编码,得到的仍然是 bytearray 数据类型的数据:

your_bytearray = bytearray('012345', 'utf-8')

print(isinstance(your_bytearray, bytearray))

运行程序:

python your_script.py
### 控制台将打印
# True

可以传入 dict 数据类型的数据,前提是它的 都是 [0, 255] 中的 int 数据类型的数据,得到的仍然是 bytearray 数据类型的数据:

your_bytearray = bytearray({0:1, 1:1, 2:2, 3:3, 4:4, 5:5})

print(isinstance(your_bytearray, bytearray))

运行程序:

python your_script.py
### 控制台将打印
# True

可以传入 listtuplesetrangebytes 等数据类型的数据,前提是它的 元素都是 [0, 255] 中的 int 数据类型的数据,得到的仍然是 bytearray 数据类型的数据:

your_list = bytearray([0,1,2,3,4,5])
your_tuple = bytearray((0,1,2,3,4,5))
your_set = bytearray({0,1,2,3,4,5})
your_range = bytearray(range(6))
your_bytes = bytearray(b'012345')

print(isinstance(your_list, bytearray))
print(isinstance(your_tuple, bytearray))
print(isinstance(your_set, bytearray))
print(isinstance(your_range, bytearray))
print(isinstance(your_bytes, bytearray))

运行程序:

python your_script.py
### 控制台将打印
# True
# True
# True
# True
# True

frozenset() 是Python的内置函数,返回语句的操作数的数据类型为 frozenset

print(isinstance(frozenset(), frozenset))

运行程序:

python your_script.py
### 控制台将打印
# True

获得的数据是空集:

for x in frozenset():
    print(x)
else:
    print('Nothing')

运行程序:

python your_script.py
### 控制台将打印
# Nothing

可以传入 strlisttuplesetrangebytesbytearay 等数据类型的数据,得到的仍是frozenset数据结构的数据:

your_str = frozenset('012345')
your_list = frozenset([0, 1, 2, 3, 4, 5])
your_tuple = frozenset((0, 1, 2, 3, 4, 5))
your_dict = frozenset({'a':0, 'b':1, 'c':2, 'd':3, 'e':4, 'f':5})
your_set = frozenset({0, 1, 2, 3, 4, 5})
your_range = frozenset(range(6))
your_bytes = frozenset(b'012345')
your_bytearray = frozenset(bytearray(b'012345'))

print(isinstance(your_str, frozenset))
print(isinstance(your_list, frozenset))
print(isinstance(your_tuple, frozenset))
print(isinstance(your_dict, frozenset))
print(isinstance(your_set, frozenset))
print(isinstance(your_range, frozenset))
print(isinstance(your_bytes, frozenset))
print(isinstance(your_bytearray, frozenset))

运行程序:

python your_script.py
### 控制台将打印
# True
# True
# True
# True
# True
# True
# True
# True

frozenset数据类型的数据若满足其元素全为 [0, 255] 中的 int 数据结构的数据,则可通过 bytearray() 强制转换为 ** bytearray** 数据类型的数据:

your_bytearray = bytearray(frozenset({0, 1, 2, 3, 4, 5}))

print(isinstance(your_bytearray, bytearray)

运行程序:

python your_script.py
### 控制台将打印
# True
索引访问

索引访问序列 特有的操作,语法位:

sequence[index]

indexint 数据类型的数据,支持 正索引(从左到右)负索引(从右到左)

  • 正索引:从 0 开始,0 表示第一个元素,1 表示第二个元素,以此类推;
  • 负索引:从 -1 开始,-1 表示最后一个元素,-2 表示倒数第二个元素,以此类推。

Python所有内置的有序数据结构 都支持 索引访问

your_str = '012345'
your_list = [0, 1, 2, 3, 4, 5]
your_tuple = (0, 1, 2, 3, 4, 5)
your_range = range(6)
your_bytes = b'012345'
your_bytearray = bytearray(b'012345')

print(your_str[0])
print(your_list[0])
print(your_tuple[0])
print(your_range[0])
print(your_bytes[0])
print(your_bytearray[0])

print('|------------')

print(your_str[-1])
print(your_list[-1])
print(your_tuple[-1])
print(your_range[-1])
print(your_bytes[-1])
print(your_bytearray[-1])

运行程序:

python your_script.py
### 控制台将打印
# 0
# 0
# 0
# 0
# 48
# 48
# |------------
# 5
# 5
# 5
# 5
# 53
# 53

如果索引超出范围,会触发 IndexError 异常:

your_str = '012345'
your_list = [0, 1, 2, 3, 4, 5]
your_tuple = (0, 1, 2, 3, 4, 5)
your_range = range(6)
your_bytes = b'012345'
your_bytearray = bytearray(b'012345')

def ier(seq):
    try:
        print(seq[6])
    except IndexError:
        print('IndexError')

ier(your_str)
ier(your_list)
ier(your_tuple)
ier(your_range)
ier(your_bytes)
ier(your_bytearray)

运行程序:

python your_script.py
### 控制台将打印
# IndexError
# IndexError
# IndexError
# IndexError 
# IndexError
# IndexError

bool 数据类型的数据可以 充当索引True 会隐式类型转换为 1False 会隐式类型转换为 06

your_str = '012345'
your_list = [0, 1, 2, 3, 4, 5]
your_tuple = (0, 1, 2, 3, 4, 5)
your_range = range(6)
your_bytes = b'012345'
your_bytearray = bytearray(b'012345')

print(your_str[True], your_str[1])
print(your_list[True], your_list[1])
print(your_tuple[True], your_tuple[1])
print(your_range[True], your_range[1])
print(your_bytes[True], your_bytes[1])
print(your_bytearray[True], your_bytearray[1])

print('|------------')

print(your_str[False], your_str[0])
print(your_list[False], your_list[0])
print(your_tuple[False], your_tuple[0])
print(your_range[False], your_range[0])
print(your_bytes[False], your_bytes[0])
print(your_bytearray[False], your_bytearray[0])

运行程序:

python your_script.py
### 控制台将打印
# 1 1
# 1 1
# 1 1
# 1 1
# 49 49
# 49 49
# |------------
# 0 0
# 0 0
# 0 0
# 0 0
# 48 48
# 48 48
切片

切片操作序列 特有的操作,语法为:

sequence[start:stop:step]
  • start:起始索引(包含),省略时默认为0;
  • stop:结束索引(不包含),省略时默认为序列末尾;
  • step:步长(默认为1),控制元素间隔,可为负数(反向切片)。

Python所有内置的有序数据结构 都支持 切片操作

your_str = '012345'
your_list = [0, 1, 2, 3, 4, 5]
your_tuple = (0, 1, 2, 3, 4, 5)
your_range = range(6)
your_bytes = b'012345'
your_bytearray = bytearray(b'012345')

print(your_str[1:])
print(your_list[1:])
print(your_tuple[1:])
print(your_range[1:])
print(your_bytes[1:])
print(your_bytearray[1:])

print('|------------')

print(your_str[:4])
print(your_list[:4])
print(your_tuple[:4])
print(your_range[:4])
print(your_bytes[:4])
print(your_bytearray[:4])

print('|------------')

print(your_str[1:4]) 
print(your_list[1:4]) 
print(your_tuple[1:4]) 
print(your_range[1:4]) 
print(your_bytes[1:4]) 
print(your_bytearray[1:4])

print('|------------')

print(your_str[::2]) 
print(your_list[::2]) 
print(your_tuple[::2]) 
print(your_range[::2]) 
print(your_bytes[::2]) 
print(your_bytearray[::2])

运行程序:

python your_script.py
### 控制台将打印:
# 12345 
# [1, 2, 3, 4, 5] 
# (1, 2, 3, 4, 5) 
# range(1, 6) 
# b'12345' 
# bytearray(b'12345') 
# |------------
# 0123 
# [0, 1, 2, 3] 
# (0, 1, 2, 3) 
# range(0, 4) 
# b'0123' 
# bytearray(b'0123') 
# |------------ 
# 123 
# [1, 2, 3] 
# (1, 2, 3) 
# range(1, 4) 
# b'123' 
# bytearray(b'123')
# |------------
# 024
# [0, 2, 4]
# (0, 2, 4)
# range(0, 6, 2)
# b'024'
# bytearray(b'024')

负数索引 从序列末尾开始计数:

your_str = '012345'
your_list = [0, 1, 2, 3, 4, 5]
your_tuple = (0, 1, 2, 3, 4, 5)
your_range = range(6)
your_bytes = b'012345'
your_bytearray = bytearray(b'012345')

print(your_str[-3:])
print(your_list[-3:])
print(your_tuple[-3:])
print(your_range[-3:])
print(your_bytes[-3:])
print(your_bytearray[-3:])

print('|------------')

print(your_str[:-2])
print(your_list[:-2])
print(your_tuple[:-2])
print(your_range[:-2])
print(your_bytes[:-2])
print(your_bytearray[:-2])

运行程序:

python your_script.py
### 控制台将打印
# 345
# [3, 4, 5]
# (3, 4, 5)
# range(3, 6)
# b'345'
# bytearray(b'345')
# |------------
# 0123
# [0, 1, 2, 3]
# (0, 1, 2, 3)
# range(0, 4)
# b'0123'
# bytearray(b'0123')

step < 0 时,会得到 反向切片,此时切片方向为 从右到左

这时候,start 默认是从索引 -1 开始的,stop 默认是到索引 0 结束的:

your_str = '012345'
your_list = [0, 1, 2, 3, 4, 5]
your_tuple = (0, 1, 2, 3, 4, 5)
your_range = range(6)
your_bytes = b'012345'
your_bytearray = bytearray(b'012345')

print(your_str[::-1]) 
print(your_list[::-1]) 
print(your_tuple[::-1]) 
print(your_range[::-1]) 
print(your_bytes[::-1]) 
print(your_bytearray[::-1])

print('|------------')

print(your_str[5:2:-1]) 
print(your_list[5:2:-1]) 
print(your_tuple[5:2:-1]) 
print(your_range[5:2:-1]) 
print(your_bytes[5:2:-1]) 
print(your_bytearray[5:2:-1])

运行程序:

python your_script.py
### 控制台将打印
# 543210
# [5, 4, 3, 2, 1, 0]
# (5, 4, 3, 2, 1, 0)
# range(5, -1, -1)
# b'543210'
# bytearray(b'543210')
# |------------
# 543
# [5, 4, 3]
# (5, 4, 3)
# range(5, 2, -1)
# b'543'
# bytearray(b'543')

当切片索引 超出范围 时,会 自动截取 序列的 有效范围

your_str = '012345'
your_list = [0, 1, 2, 3, 4, 5]
your_tuple = (0, 1, 2, 3, 4, 5)
your_range = range(6)
your_bytes = b'012345'
your_bytearray = bytearray(b'012345')

print(your_str[:100])
print(your_list[:100])
print(your_tuple[:100])
print(your_range[:100])
print(your_bytes[:100])
print(your_bytearray[:100])

print('|------------')

print(your_str[-100:5])
print(your_list[-100:5])
print(your_tuple[-100:5])
print(your_range[-100:5])
print(your_bytes[-100:5])
print(your_bytearray[-100:5])

运行程序:

python your_script.py
### 控制台将打印
# 012345
# [0, 1, 2, 3, 4, 5]
# (0, 1, 2, 3, 4, 5)
# range(0, 6)
# b'012345'
# bytearray(b'012345')
# |------------
# 01234
# [0, 1, 2, 3, 4]
# (0, 1, 2, 3, 4)
# range(0, 5)
# b'01234'
# bytearray(b'01234')

当逻辑上 无元素可截取 时,得到 空序列

your_str = '012345'
your_list = [0, 1, 2, 3, 4, 5]
your_tuple = (0, 1, 2, 3, 4, 5)
your_range = range(6)
your_bytes = b'012345'
your_bytearray = bytearray(b'012345')

def emp(seq):
    for x in seq[5:2]:
        print(x)
    else:
        print('|------------')

emp(your_str)
emp(your_list)
emp(your_tuple)
emp(your_range)
emp(your_bytes)
emp(your_str)
emp(your_bytearray)

运行程序:

python your_script.py
### 控制台将打印
# |------------
# |------------
# |------------
# |------------
# |------------
# |------------

step 不能为 0,否则触发 ValueError 错误:

your_str = '012345'
your_list = [0, 1, 2, 3, 4, 5]
your_tuple = (0, 1, 2, 3, 4, 5)
your_range = range(6)
your_bytes = b'012345'
your_bytearray = bytearray(b'012345')

def ver(seq):
    try:
        print(seq[::0])
    except ValueError:
        print('ValueError')

ver(your_str)
ver(your_list)
ver(your_tuple)
ver(your_range)
ver(your_bytes)
ver(your_bytearray)

运行程序:

python your_script.py
### 控制台将打印
# ValueError
# ValueError
# ValueError
# ValueError
# ValueError
# ValueError
内置函数 - 可迭代数据结构

如果一个 数据结构 存在不重复地取遍其存储元素的方式,则称它为 可迭代数据结构,称它具有 可迭代性

iter()next() 是Python的内置函数。

对于一个 可迭代数据结构,可以用 iter() 获取它的 不重复地取遍其存储元素的方式,称这种方法为 迭代器,语法为:

your_iter = iter(your_container)

迭代器 一旦获取,它的 取遍方式 就是固定的,可以应用 next() 从数据结构中进行获取元素,语法为:

next(your_iter)

Python目前内置的数据结构都是 可迭代数据结构

your_str = '012345'
your_list = [0, 1, 2, 3, 4, 5]
your_tuple = (0, 1, 2, 3, 4, 5)
your_dict = {'a':0, 'b':1, 'c':2, 'd':3, 'e':4, 'f':5}
your_set = {0, 1, 2, 3, 4, 5}
your_frozenset = frozenset({0, 1, 2, 3, 4, 5})
your_range = range(6)
your_bytes = b'012345'
your_bytearray = bytearray(b'012345')

def seq_it(seq):
    seq_iter = iter(seq)
    print(
        next(seq_iter),
        next(seq_iter),
        next(seq_iter),
        next(seq_iter),
        next(seq_iter),
        next(seq_iter)
    )

seq_it(your_str)
seq_it(your_list)
seq_it(your_tuple)
seq_it(your_dict)
seq_it(your_set)
seq_it(your_frozenset)
seq_it(your_range)
seq_it(your_bytes)
seq_it(your_bytearray)

运行程序:

python your_script.py
### 控制台将打印
# 0 1 2 3 4 5
# 0 1 2 3 4 5
# 0 1 2 3 4 5
# a b c d e f
# 0 1 2 3 4 5
# 0 1 2 3 4 5
# 0 1 2 3 4 5
# 48 49 50 51 52 53
# 48 49 50 51 52 53

迭代器 是会 耗尽不能重置,当取完所有元素继续取将会抛出 StopIteration 的异常:

your_str = '012345'
your_list = [0, 1, 2, 3, 4, 5]
your_tuple = (0, 1, 2, 3, 4, 5)
your_dict = {'a':0, 'b':1, 'c':2, 'd':3, 'e':4, 'f':5}
your_set = {0, 1, 2, 3, 4, 5}
your_frozenset = frozenset({0, 1, 2, 3, 4, 5})
your_range = range(6)
your_bytes = b'012345'
your_bytearray = bytearray(b'012345')

def whi_it(seq):
    while True:
        try:
            print(next(seq))
        except StopIteration:
            print('|------------')
            break

whi_it(iter(your_str))
whi_it(iter(your_list))
whi_it(iter(your_tuple))
whi_it(iter(your_dict))
whi_it(iter(your_set))
whi_it(iter(your_frozenset))
whi_it(iter(your_range))
whi_it(iter(your_bytes))
whi_it(iter(your_bytearray))

运行程序:

python your_script.py
### 控制台将打印
# 0
# 1
# 2
# 3
# 4
# 5
# |------------
# 0
# 1
# 2
# 3
# 4
# 5
# |------------
# 0
# 1
# 2
# 3
# 4
# 5
# |------------
# a
# b
# c
# d
# e
# f
# |------------
# 0
# 1
# 2
# 3
# 4
# 5
# |------------
# 0
# 1
# 2
# 3
# 4
# 5
# |------------
# 0
# 1
# 2
# 3
# 4
# 5
# |------------
# 48
# 49
# 50
# 51
# 52
# 53
# |------------
# 48
# 49
# 50
# 51
# 52
# 53
# |------------

迭代器 也是 可迭代数据结构,可以用iter() 获取到 自身

your_str = '012345'
your_list = [0, 1, 2, 3, 4, 5]
your_tuple = (0, 1, 2, 3, 4, 5)
your_dict = {'a':0, 'b':1, 'c':2, 'd':3, 'e':4, 'f':5}
your_set = {0, 1, 2, 3, 4, 5}
your_frozenset = frozenset({0, 1, 2, 3, 4, 5})
your_range = range(6)
your_bytes = b'012345'
your_bytearray = bytearray(b'012345')

def eq_iter(seq):
    seq_iter = iter(seq)
    print(seq_iter is iter(seq_iter))

eq_iter(your_str)
eq_iter(your_list)
eq_iter(your_tuple)
eq_iter(your_dict)
eq_iter(your_set)
eq_iter(your_frozenset)
eq_iter(your_range)
eq_iter(your_bytes)
eq_iter(your_bytearray)

运行程序:

python your_script.py
### 控制台将打印
# True
# True
# True
# True
# True
# True
# True
# True
# True

for 开头的 循环语句 相当于使用了 iter()next(),并且自动处理了 StopIteration 异常,因此它也常被称为 迭代循环

your_str = '012345'
your_list = [0, 1, 2, 3, 4, 5]
your_tuple = (0, 1, 2, 3, 4, 5)
your_dict = {'a':0, 'b':1, 'c':2, 'd':3, 'e':4, 'f':5}
your_set = {0, 1, 2, 3, 4, 5}
your_frozenset = frozenset({0, 1, 2, 3, 4, 5})
your_range = range(6)
your_bytes = b'012345'
your_bytearray = bytearray(b'012345')

def it_for(seq):
    for x in seq:
        print(x)
    print('|------------')

it_for(your_str)
it_for(your_list)
it_for(your_tuple)
it_for(your_dict)
it_for(your_set)
it_for(your_frozenset)
it_for(your_range)
it_for(your_bytes)
it_for(your_bytearray)

运行程序:

python your_script.py
### 控制台将打印
# 0
# 1
# 2
# 3
# 4
# 5
# |------------
# 0
# 1
# 2
# 3
# 4
# 5
# |------------
# 0
# 1
# 2
# 3
# 4
# 5
# |------------
# a
# b
# c
# d
# e
# f
# |------------
# 0
# 1
# 2
# 3
# 4
# 5
# |------------
# 0
# 1
# 2
# 3
# 4
# 5
# |------------
# 0
# 1
# 2
# 3
# 4
# 5
# |------------
# 48
# 49
# 50
# 51
# 52
# 53
# |------------
# 48
# 49
# 50
# 51
# 52
# 53
# |------------

这说明,如果不是 可迭代数据结构,使用 for 开头的循环语句会引发 TypeError 错误:

try:
    for x in 1:
        print(x)
except TypeError:
    print('TypeError')

运行程序:

python your_script.py
### 控制台将打印
# TypeError
内置函数 - 索引绑定

在使用 迭代循环 时,常常需要同时知晓元素和它在有序数据结构中的索引

给定一组 int 数据类型的数据,返回一组数据,要求为其元素为原数据组中当前元素与后一个元素的差值

Python 内置了一些函数帮助处理类似问题。

len() 是Python的内置函数,它可以获取 序列长度

your_str = '012345'
your_list = [0, 1, 2, 3, 4, 5]
your_tuple = (0, 1, 2, 3, 4, 5)
your_range = range(6)
your_bytes = b'012345'
your_bytearray = bytearray(b'012345')

print(len(your_str))
print(len(your_list))
print(len(your_tuple))
print(len(your_range))
print(len(your_bytes))
print(len(your_bytearray))

运行程序:

python your_script.py
### 控制台将打印
# 6
# 6
# 6
# 6
# 6
# 6

解法1

def diff(*items):
    result = []
    try:
        for idx in range(len(items)):
            result += [items[idx] - items[idx + 1]]
    except IndexError:
        return result

result = diff(1, 3, -4, 2)
print(result)

运行程序:

python your_script.py
### 控制台将打印
# [-2, 7, -6]

enumerate() 是Python的 内置函数,可以将 序列索引元素 打包成由 tuple 数据类型的数据组成的 迭代器

your_str = '012345'
your_list = [0, 1, 2, 3, 4, 5]
your_tuple = (0, 1, 2, 3, 4, 5)
your_range = range(6)
your_bytes = b'012345'
your_bytearray = bytearray(b'012345')

def pri(seq):
    while True:
        try:
            tup = next(seq)
            print(isinstance(tup, tuple), *tup)
        except StopIteration:
            print('|------------')
            break
    
pri(enumerate(your_str))
pri(enumerate(your_list))
pri(enumerate(your_tuple))
pri(enumerate(your_range))
pri(enumerate(your_bytes))
pri(enumerate(your_bytearray))

运行程序:

python your_script.py
### 控制台将打印
# True 0 0
# True 1 1
# True 2 2
# True 3 3
# True 4 4
# True 5 5
# |------------
# True 0 0
# True 1 1
# True 2 2
# True 3 3
# True 4 4
# True 5 5
# |------------
# True 0 0
# True 1 1
# True 2 2
# True 3 3
# True 4 4
# True 5 5
# |------------
# True 0 0
# True 1 1
# True 2 2
# True 3 3
# True 4 4
# True 5 5
# |------------
# True 0 48
# True 1 49
# True 2 50
# True 3 51
# True 4 52
# True 5 53
# |------------
# True 0 48
# True 1 49
# True 2 50
# True 3 51
# True 4 52
# True 5 53
# |------------

解法2

def diff(*items):
    result = []
    try:
        for idx, x in enumerate(items):
            result += [x - items[idx + 1]]
    except IndexError:
        return result

result = diff(1, 3, -4, 2)
print(result)

运行程序:

python your_script.py
### 控制台将打印
# [-2, 7, -6]
容器推导式

考虑以下的问题:

给定一个 dict 数据类型的数据,交换它的键值

Python 有一类解决这类由一个 数据结构 创建 另一个数据结构 的语法,那就是 容器推导式

有四种 容器推导式,分别是 列表推导式元组推导式字典推导式集合推导式,负责通过一个可迭代数据结构,创建 listtupledictset 数据类型的数据。

[表达式for若干操作数in可迭代数据结构] 的组合称为 列表推导式,负责创建 list 数据类型的数据:

your_list = [x * 2 + 1 for x in range(6)]

print(isinstance(your_list, list))
print(your_list)

运行程序:

python your_script.py
### 控制台将打印
# True
# [1, 3, 5, 7, 9, 11]

{表达式1:表达式2for若干操作数in可迭代数据结构} 的组合称为 字典推导式,负责创建 dict 数据类型的数据:

your_dict = {x:x * 2 + 1 for x in range(6)}

print(isinstance(your_dict, dict))
print(your_dict)

运行程序:

### 控制台将打印
# True
# {0: 1, 1: 3, 2: 5, 3: 7, 4: 9, 5: 11}

元组推导式集合推导式 比较少使用,与 列表推导式字典推导式 类似,语法为:

  • 元组推导式tuple(表达式for操作数in可迭代数据结构)的组合,创建tuple数据类型的数据;
  • 集合推导式{表达式for操作数in可迭代数据结构}的组合,创建set数据类型的数据;
your_tuple = tuple(x * 2 + 1 for x in range(6))
your_set = {x * 2 + 1 for x in range(6)}

print(isinstance(your_tuple, tuple))
print(your_tuple)

print('|------------')

print(isinstance(your_set, set))
print(your_set)

运行程序:

python your_script.py
### 控制台将打印
# True
# (1, 3, 5, 7, 9, 11)
# |------------
# True
# {1, 3, 5, 7, 9, 11}

解法

your_dict = {your_dict[k]:k for k in your_dict}

容器推导式 可以执行 过滤操作 ,语法为:

[表达式1 for 操作数 in 可迭代数据结构 if 表达式0]
{表达式1:表达式2 for 操作数 in 可迭代数据结构 if 表达式0}
tuple(表达式1 for 操作数 in 可迭代数据结构 if 表达式0)
{表达式1 for 操作数 in 可迭代数据结构 if 表达式0}

创建的 数据结构 只会存储满足 表达式0 的运算结果可转换为 True 的元素:

your_list = [x * 2 + 1 for x in range(6)]
your_list_filter = [x * 2 + 1 for x in range(6) if x % 2 == 0]

your_tuple = tuple(x * 2 + 1 for x in range(6))
your_tuple_filter = tuple(x * 2 + 1 for x in range(6) if x % 2 == 0)

your_dict = {x:x * 2 + 1 for x in range(6)}
your_dict_filter = {x:x * 2 + 1 for x in range(6) if x % 2 == 0}

your_set = {x * 2 + 1 for x in range(6)}
your_set_filter = {x * 2 + 1 for x in range(6) if x % 2 == 0}

print(your_list, your_list_filter)
print(your_tuple, your_tuple_filter)
print(your_dict, your_dict_filter)
print(your_set, your_set_filter)

运行程序:

python your_script.py
### 控制台将打印
#[1, 3, 5, 7, 9, 11] [1, 5, 9]
#(1, 3, 5, 7, 9, 11) (1, 5, 9)
#{0: 1, 1: 3, 2: 5, 3: 7, 4: 9, 5: 11} {0: 1, 2: 5, 4: 9}
#{1, 3, 5, 7, 9, 11} {1, 5, 9}
生成器函数与生成器表达式

考虑以下问题:

有一组序列 ‘A’, ‘B’, ‘C’, …,从 ‘A’ 开始无限循环,存储它

yield 是Python的 关键字,一般的函数在处理到返回语句时,自身的状态就会立即销毁,而 yield 可以暂停保留函数的状态,给原本该 魂归故里 的函数改判 死缓 甚至 ** 无期**,这就让其有存储无限序列的潜能。

yield操作数;的组合称为 生成语句,解释器执行到它,则暂停函数的状态 并给出 操作数序列

def fibonacci(n):
    a, b = 0, 1
    while n >= 0:
        yield a
        a, b = b, a + b
        n -= 1

gen = fibonacci(10)
for x in gen:
    print(x)

运行程序:

python your_script.py
### 控制台将打印
# 0
# 1
# 1
# 2
# 3
# 5
# 8
# 13
# 21
# 34
# 55

可以看出,操作数序列 是一个 可迭代数据结构,那么,它是否为 迭代器呢?

def fibonacci(n):
    a, b = 0, 1    
    while n >= 0:        
        yield a        
        a, b = b, a + b        
        n -= 1

gen = fibonacci(10)
print(next(gen))

运行程序:

python your_script.py
### 控制台将打印
# 0

因此,它是一个 迭代器,我们称这种方式得到的 迭代器生成器,称能有效执行 生成语句 得到 生成器 的函数为 生成器函数,以此为基础,我们就可以解决开头给出的问题。

解法

def yield_abc():
    while True:
        for x in 'ABC':
            yield x

abc_gen = yield_abc()
print([next(abc_gen) for _ in range(10)])
print([next(abc_gen) for _ in range(10)])
print([next(abc_gen) for _ in range(10)])

运行程序:

python your_script.py
### 控制台将打印
# ['A', 'B', 'C', 'A', 'B', 'C', 'A', 'B', 'C', 'A']
# ['B', 'C', 'A', 'B', 'C', 'A', 'B', 'C', 'A', 'B']
# ['C', 'A', 'B', 'C', 'A', 'B', 'C', 'A', 'B', 'C']

Python 中有类似 容器推导式生成器表达式语法类似,然而创建的是一个 生成器,语法为:

(表达式1 for 操作数 in 可迭代数据结构 if 表达式0)

一个示例,获取平方数

squares_gen = (x ** 2 for x in range(10))

print([next(squares_gen) for _ in range(5)])
print([next(squares_gen) for _ in range(5)])

运行程序:

python your_script.py
### 控制台将打印
# [0, 1, 4, 9, 16]
# [25, 36, 49, 64, 81]

生成器表达式也可以创建 无限序列,前提是 可迭代数据结构 也是 无限序列

def stagger():
    n = 1.0
    while True:
        for sign in [-1, 1]:
            yield sign * n
            n += 1.0

stag_gen = stagger()
posi_gen = ((n*n)**0.5 for n in stag_gen)

print([next(posi_gen) for _ in range(10)])
print([next(posi_gen) for _ in range(10)])
print([next(posi_gen) for _ in range(10)])
print([next(posi_gen) for _ in range(10)])
print([next(posi_gen) for _ in range(10)])

运行程序:

python your_script.py
### 控制台将打印
# [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
# [11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0]
# [21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0]
# [31.0, 32.0, 33.0, 34.0, 35.0, 36.0, 37.0, 38.0, 39.0, 40.0]
# [41.0, 42.0, 43.0, 44.0, 45.0, 46.0, 47.0, 48.0, 49.0, 50.0]
数据管道

考虑以下问题:

给定一个无限自然数序列,创建一个可模三的无限正负交错整数序列

你可以用 函数闭包 的性质解决它。

解法1

def target():
    time = 0
    def natn():
        nonlocal time
        num = time
        def mod3():
            nonlocal num
            nonlocal time
            num = (num + 1) * 3
            def stag():
                nonlocal num
                nonlocal time
                return num * (-1) ** (time - 1)
            return stag
        time += 1
        return mod3
    return natn

tg = target()

print([tg()()() for _ in range(10)])
print([tg()()() for _ in range(10)])
print([tg()()() for _ in range(10)])
print([tg()()() for _ in range(10)])
print([tg()()() for _ in range(10)])

运行程序:

python your_script.py
### 控制台将打印
# [3, -6, 9, -12, 15, -18, 21, -24, 27, -30]
# [33, -36, 39, -42, 45, -48, 51, -54, 57, -60]
# [63, -66, 69, -72, 75, -78, 81, -84, 87, -90]
# [93, -96, 99, -102, 105, -108, 111, -114, 117, -120]
# [123, -126, 129, -132, 135, -138, 141, -144, 147, -150]

数据 从最原始的自然数 开始,经过 natn → mod3 → stag 三个嵌套的函数转换,每一层 负责 一项 具体的处理任务,最后通过 层层调用 tg()()() ,递归地访问 上层作用域 来得到 最终值。这种设计让每个函数都只关注 单一的转换逻辑

然而,一旦 临时变换需求,哪怕只是要求 给每个数加 1,都 必须更改 所有已有函数。

造成这种 窘境 的原因在于,函数 和它的 内部函数层级是不对等的,一旦给套了一层新函数,它 所有的内部函数,以及 内部函数的内部函数 都必须跟着 加深一层牵一发而动全身

因此,你 一定 想要了解,如何把这些 负责具体处理任务 的函数提升到 同一层级,这就是 数据管道

解法2

def natn():
    n = 0
    while True:
        yield n        
        n += 1

def mod3(na):
    while True:
        yield (next(na) + 1) * 3

def stag(mo):
    while True:
        for sign in [1,-1]:
            yield sign * next(mo)

na = natn()
mo = mod3(na)
tg = stag(mo)

print([next(tg) for _ in range(10)])
print([next(tg) for _ in range(10)])
print([next(tg) for _ in range(10)])
print([next(tg) for _ in range(10)])
print([next(tg) for _ in range(10)])

运行程序:

python your_script.py
### 控制台将打印
# [3, -6, 9, -12, 15, -18, 21, -24, 27, -30]
# [33, -36, 39, -42, 45, -48, 51, -54, 57, -60]
# [63, -66, 69, -72, 75, -78, 81, -84, 87, -90]
# [93, -96, 99, -102, 105, -108, 111, -114, 117, -120]
# [123, -126, 129, -132, 135, -138, 141, -144, 147, -150]

数据管道 的核心概念分为四个方面

  • 阶段性每个阶段 只负责 一个具体任务
  • 流动性按顺序 处理任务,数据被层层传递
  • 按需取值:可 分批次 处理任务;
  • 可解耦性:管道的 每个部分 可按照要求 任意更换重新组装,就可以胜任 新的任务
解法阶段性流动性按需取值可解耦性
解法1
解法1

无限序列 为例是为了让你直观感受到 数据管道 的优势,接下来看 有限序列 的例子:

your_list1 = [0,1,1,2,3,5]
your_list2 = [1,-1,2,-3,5,-8]
your_list3 = [1,3,5,7,9,11]

mul2 = lambda seq: (x * 2 for x in seq)
div2 = lambda seq: (x // 2 for x in seq)
add1 = lambda seq: (x + 1 for x in seq)
sub1 = lambda seq: (x - 1 for x in seq)

gen1 = mul2(add1(your_list1))
gen2 = sub1(mul2(your_list2))
gen3 = div2(div2(your_list3))

print([next(gen1) for _ in range(6)])
print([next(gen2) for _ in range(6)])
print([next(gen3) for _ in range(6)])

运行程序:

python your_script.py
### 控制台将打印
# [2, 4, 4, 6, 8, 12]
# [1, -3, 3, -7, 9, -17]
# [0, 0, 1, 1, 2, 2]
内置函数 - 绑定和解包

考虑以下问题:

给定一个无穷正负交错非零整数序列和一个无穷正整数序列,创建一个无穷差值整数序列

这次的问题将 同时操作 两个序列,Python 内置了 zip() 函数处理这类 多序列 的问题。

它将两个 序列tuple 数据类型的数据绑定在一起,并给出一个 迭代器

your_str = '012345'
your_list = [0, 1, 2, 3, 4, 5]
your_tuple = (0, 1, 2, 3, 4, 5)
your_range = range(6)
your_bytes = b'012345'
your_bytearray = bytearray(b'012345')

def all_tuple(seq1, seq2):
    seq_iter = zip(seq1, seq2)
    zip_seq = []
    is_all_tuple = True
    while True:
        try:
            x = next(seq_iter)
            zip_seq += [x]
            b = isinstance(x, tuple)
            is_all_tuple = is_all_tuple and b
        except StopIteration:
            print(is_all_tuple, zip_seq)
            break

all_tuple(your_str, your_list)
all_tuple(your_str, your_tuple)
all_tuple(your_str, your_range)
all_tuple(your_str, your_bytes)
all_tuple(your_str, your_bytearray)
all_tuple(your_list, your_tuple)
all_tuple(your_list, your_range)
all_tuple(your_list, your_bytes)
all_tuple(your_list, your_bytearray)
all_tuple(your_tuple, your_range)
all_tuple(your_tuple, your_bytes)
all_tuple(your_tuple, your_bytearray)
all_tuple(your_range, your_bytes)
all_tuple(your_range, your_bytearray)
all_tuple(your_bytes, your_bytearray)

运行程序:

python your_script.py
### 控制台将打印
# True [('0', 0), ('1', 1), ('2', 2), ('3', 3), ('4', 4), ('5', 5)]
# True [('0', 0), ('1', 1), ('2', 2), ('3', 3), ('4', 4), ('5', 5)]
# True [('0', 0), ('1', 1), ('2', 2), ('3', 3), ('4', 4), ('5', 5)]
# True [('0', 48), ('1', 49), ('2', 50), ('3', 51), ('4', 52), ('5', 53)]
# True [('0', 48), ('1', 49), ('2', 50), ('3', 51), ('4', 52), ('5', 53)]
# True [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]
# True [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]
# True [(0, 48), (1, 49), (2, 50), (3, 51), (4, 52), (5, 53)]
# True [(0, 48), (1, 49), (2, 50), (3, 51), (4, 52), (5, 53)]
# True [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]
# True [(0, 48), (1, 49), (2, 50), (3, 51), (4, 52), (5, 53)]
# True [(0, 48), (1, 49), (2, 50), (3, 51), (4, 52), (5, 53)]
# True [(0, 48), (1, 49), (2, 50), (3, 51), (4, 52), (5, 53)]
# True [(0, 48), (1, 49), (2, 50), (3, 51), (4, 52), (5, 53)]
# True [(48, 48), (49, 49), (50, 50), (51, 51), (52, 52), (53, 53)]

解法

def stag():
    n = 1
    while True:
        yield n
        yield -n
        n += 1

def posi():
    n = 1
    while True:
        yield n
        n += 1

zip_gen = (x - y for x, y in zip(stag(), posi()))

print([next(zip_gen) for _ in range(10)])
print([next(zip_gen) for _ in range(10)])
print([next(zip_gen) for _ in range(10)])
print([next(zip_gen) for _ in range(10)])
print([next(zip_gen) for _ in range(10)])

运行程序:

python your_script.py
### 控制台将打印
# [0, -3, -1, -6, -2, -9, -3, -12, -4, -15]
# [-5, -18, -6, -21, -7, -24, -8, -27, -9, -30]
# [-10, -33, -11, -36, -12, -39, -13, -42, -14, -45]
# [-15, -48, -16, -51, -17, -54, -18, -57, -19, -60]
# [-20, -63, -21, -66, -22, -69, -23, -72, -24, -75]
内置函数 - 映射和过滤

Python 的 容器推导式,主要侧重于 创建数据结构,而不适用于 数据处理,Python 内置 了一些函数用以替代它们在 数据处理 方面的工作。

考虑以下问题:

给定一个无穷奇数序列,令其每个数都变成它的二倍

你仍然可以用 生成器表达式

解法1

def odd():
    n = 0
    while True:
        yield n * 2 + 1
        n += 1

gen = (x * 2 for x in odd())

print([next(gen) for _ in range(10)])
print([next(gen) for _ in range(10)])
print([next(gen) for _ in range(10)])
print([next(gen) for _ in range(10)])
print([next(gen) for _ in range(10)])

运行程序:

python your_script.py
### 控制台将打印
# [2, 6, 10, 14, 18, 22, 26, 30, 34, 38]
# [42, 46, 50, 54, 58, 62, 66, 70, 74, 78]
# [82, 86, 90, 94, 98, 102, 106, 110, 114, 118]
# [122, 126, 130, 134, 138, 142, 146, 150, 154, 158]
# [162, 166, 170, 174, 178, 182, 186, 190, 194, 198]

map() 是Python的一个 内置函数,它能做到一样的功能,并返回一个 迭代器

解法2

def odd():
    n = 0
    while True:
        yield n * 2 + 1
        n += 1

gen = map(lambda x: x * 2, odd())

print([next(gen) for _ in range(10)])
print([next(gen) for _ in range(10)])
print([next(gen) for _ in range(10)])
print([next(gen) for _ in range(10)])
print([next(gen) for _ in range(10)])

运行程序:

python your_script.py
### 控制台将打印
# [2, 6, 10, 14, 18, 22, 26, 30, 34, 38]
# [42, 46, 50, 54, 58, 62, 66, 70, 74, 78]
# [82, 86, 90, 94, 98, 102, 106, 110, 114, 118]
# [122, 126, 130, 134, 138, 142, 146, 150, 154, 158]
# [162, 166, 170, 174, 178, 182, 186, 190, 194, 198]

看着很类似,不过,map() 函数可以同时操作 多个可迭代数据结构

回顾以下问题:

给定一个无穷正负交错非零整数序列和一个无穷正整数序列,创建一个无穷差值整数序列

你可以再次用 zip() 函数:

解法1

def stag():
    n = 1
    while True:
        yield n
        yield -n
        n += 1

def posi():
    n = 1
    while True:
        yield n
        n += 1

zip_gen = (x - y for x, y in zip(stag(), posi()))

print([next(zip_gen) for _ in range(10)])
print([next(zip_gen) for _ in range(10)])
print([next(zip_gen) for _ in range(10)])
print([next(zip_gen) for _ in range(10)])
print([next(zip_gen) for _ in range(10)])

运行程序:

python your_script.py
### 控制台将打印
# [0, -3, -1, -6, -2, -9, -3, -12, -4, -15]
# [-5, -18, -6, -21, -7, -24, -8, -27, -9, -30]
# [-10, -33, -11, -36, -12, -39, -13, -42, -14, -45]
# [-15, -48, -16, -51, -17, -54, -18, -57, -19, -60]
# [-20, -63, -21, -66, -22, -69, -23, -72, -24, -75]

现在使用 map() 函数:

def stag():
    n = 1
    while True:
        yield n
        yield -n
        n += 1

def posi():
    n = 1
    while True:
        yield n
        n += 1

map_iter = map(lambda x, y: x - y, stag(), posi())

print([next(map_iter) for _ in range(10)])
print([next(map_iter) for _ in range(10)])
print([next(map_iter) for _ in range(10)])
print([next(map_iter) for _ in range(10)])
print([next(map_iter) for _ in range(10)])

运行程序:

python your_script.py
### 控制台将打印
# [0, -3, -1, -6, -2, -9, -3, -12, -4, -15]
# [-5, -18, -6, -21, -7, -24, -8, -27, -9, -30]
# [-10, -33, -11, -36, -12, -39, -13, -42, -14, -45]
# [-15, -48, -16, -51, -17, -54, -18, -57, -19, -60]
# [-20, -63, -21, -66, -22, -69, -23, -72, -24, -75]

考虑以下问题:

给定一个无限自然数序列,创建一个无限不可被模三的自然数序列

你仍然可以用 生成器表达式

解法1

def natn():
    n = 0
    while True:
        yield n
        n += 1

gen = (x for x in natn() if x % 3 != 0)

print([next(gen) for _ in range(10)])
print([next(gen) for _ in range(10)])
print([next(gen) for _ in range(10)])
print([next(gen) for _ in range(10)])
print([next(gen) for _ in range(10)])

运行程序:

python your_script.py
### 控制台将打印
# [1, 2, 4, 5, 7, 8, 10, 11, 13, 14]
# [16, 17, 19, 20, 22, 23, 25, 26, 28, 29]
# [31, 32, 34, 35, 37, 38, 40, 41, 43, 44]
# [46, 47, 49, 50, 52, 53, 55, 56, 58, 59]
# [61, 62, 64, 65, 67, 68, 70, 71, 73, 74]

filter() 是Python的一个 内置函数,它能做到一样的功能,并返回一个 迭代器

解法2

def natn():
    n = 0
    while True:
        yield n
        n += 1

gen = filter(lambda x: x % 3 != 0, natn())

print([next(gen) for _ in range(10)])
print([next(gen) for _ in range(10)])
print([next(gen) for _ in range(10)])
print([next(gen) for _ in range(10)])
print([next(gen) for _ in range(10)])

运行程序:

python your_script.py
### 控制台将打印
# [1, 2, 4, 5, 7, 8, 10, 11, 13, 14]
# [16, 17, 19, 20, 22, 23, 25, 26, 28, 29]
# [31, 32, 34, 35, 37, 38, 40, 41, 43, 44]
# [46, 47, 49, 50, 52, 53, 55, 56, 58, 59]
# [61, 62, 64, 65, 67, 68, 70, 71, 73, 74]
内置函数 - 数据特征

数据处理 时,可能需要获取 数据组 的特征,Python 内置 了大量函数处理这部分内容。

abs() 获取 数值数据 的绝对值,若是 复数 则获取模:

your_list1 = [-3, -1, -4, -1, 5, 9]
your_list2 = [0.8, -6.1, 1.6, -8.0]
your_list3 = [1 + 1j, 1 + 9j, 4 + 1j, 5 + 9j, 1 + 8j, 4 + 0j]

print([x for x in map(abs, your_list1)])
print([x for x in map(abs, your_list2)])
print([x for x in map(abs, your_list3)])

运行程序:

python your_script.py
### 控制台将打印
# [3, 1, 4, 1, 5, 9]
# [0.8, 6.1, 1.6, 8.0]
# [1.4142135623730951, 9.055385138137417, 4.123105625617661, 10.295630140987, 8.06225774829855, 4.0]

round()实数数据 舍入到指定小数位

your_list = [1.234, -4.13, 2.333, 8.10]

round2 = lambda x: round(x, ndigits = 2)
print([x for x in map(round2, your_list)])

运行程序:

python your_script.py
### 控制台将打印
# [1.23, -4.13, 2.33, 8.1]

max()min() 返回 数值数据组最大 (小) 值

your_list = [1, 0, 3, -8, 5, -6, 7]

print(max(your_list))
print(min(your_list))

运行程序:

python your_script.py
### 控制台将打印
# 7
# -8

sum() 计算 数值数据组 的元素总和:

your_list = [1, 0, 3, 2, 1, 5, 4]

print(sum(your_list))

运行程序:

python your_script.py
### 控制台将打印
# 16
内置函数 - 布尔规约

考虑以下问题:

给定一组数据,判断是否全为数值

解法1

def your_reduce(func, iterable):
    it = iter(iterable)
    try:
        result = next(it)
    except StopIteration:
        print('StopIteration')
    for elem in it:
        result = func(result, elem)
    return result

your_all = lambda x, y: x and y
is_digit = lambda x: isinstance(x, (int, float))
map_bool = lambda x: map(is_digit, x)
all_digit = lambda *x: your_reduce(your_all, map_bool(x))

print(all_digit(1, 2, 3.5))
print(all_digit(1, '2', 3.5))
print(all_digit(0, -1, 2.0))
print(all_digit('abc', 123, 4.5))

运行程序:

python your_script.py
### 控制台将打印
# True
# False
# True
# False

all() 是Python的内置函数,可以简化操作。

解法2

is_digit = lambda x: isinstance(x, (int, float))
map_bool = lambda x: map(is_digit, x)
all_digit = lambda *x: all(map_bool(x))

print(all_digit(1, 2, 3.5))
print(all_digit(1, '2', 3.5))
print(all_digit(0, -1, 2.0))
print(all_digit('abc', 123, 4.5))

这被称为 真值规约,相应的,any() 的行为与 all 相反。

考虑以下问题:

给定一组数据,判断是否全不是数值

解法

is_digit = lambda x: isinstance(x, (int, float))
map_bool = lambda x: map(is_digit, x)
any_digit = lambda *x: any(map_bool(x))

print(any_digit('1', '2', '3.5'))
print(any_digit(1, '2', 3.5))
print(any_digit(0, '-1', '2.0'))
print(any_digit('abc', 123, 4.5))

运行程序:

python your_script.py
### 控制台将打印
# False
# True
# True
# True

这被称为 假值规约,两种统称 布尔规约

内置方法 - 排序

给定以下表格:

姓名年龄工种月薪(元)学历KPI
王思远28软件工程师18500硕士1.12
李雨桐34销售经理12600本科0.95
张浩然22客服代表4800高中0.78
陈晓敏45财务主管21800硕士1.05
刘子轩29数据分析师16700本科0.89
赵若曦37产品经理27500博士1.18
黄一鸣31运维工程师15200大专1.03
周雅雯26市场专员8900本科0.82
吴泽宇41人力资源专员11200硕士0.97
徐安然33设计师14500本科1.11

问题1

按年龄升序排序

解法

your_dict = {
    'WangSiyuan' : {
        'age': 28,
        'work': 'software_engineer',
        'salary': 18500,
        'qualify': 'master',
        'kpi': 1.12,
    },
    'LiYutong' : {
        'age': 34,
        'work': 'sales_manager',
        'salary': 12600,
        'qualify': 'undergraduate',
        'kpi': 0.95,
    },
    'ZhangHaoran' : {
        'age': 22,
        'work': 'cs_representative',
        'salary': 4800,
        'qualify': 'high_school',
        'kpi': 0.78,
    },
    'ChenXiaomin' : {
        'age': 45,
        'work': 'treasurer',
        'salary': 21800,
        'qualify': 'master',
        'kpi': 1.05,
    },
    'LiuZixuan' : {
        'age': 29,
        'work': 'data_analyst',
        'salary': 16700,
        'qualify': 'undergraduate',
        'kpi': 0.89,
    },
    'ZhaoRuoxi' : {
        'age': 37,
        'work': 'product_manager',
        'salary': 27500,
        'qualify': '',
        'kpi': 1.18,
    },
    'HuangYiming' : {
        'age': 31,
        'work': 'oam_engineer',
        'salary': 15200,
        'qualify': 'college',
        'kpi': 1.03,
    },
    'ZhouYawen' : {
        'age': 26,
        'work': 'marketing_specialist',
        'salary': 8900,
        'qualify': 'undergraduate',
        'kpi': 0.82,
    },
    'WuZeyu' : {
        'age': 41,
        'work': 'hr_specialist',
        'salary': 11200,
        'qualify': 'master',
        'kpi': 0.97,
    },
    'XuAnran' : {
        'age': 33,
        'work': 'designer',
        'salary': 14500,
        'qualify': 'undergraduate',
        'kpi': 1.11,
    },
}

def your_sorted(iterable, key = lambda x: x, reverse = False):
    items = list(iterable)
    n = len(items)

    for i in range(n - 1):
        swapped = False
        for j in range(n - 1 - i):
            key_j = key(items[j])
            key_j1 = key(items[j + 1])
            if not reverse:
                if key_j > key_j1:
                    items[j], items[j + 1] = items[j + 1], items[j]
                    swapped = True
            else:
                if key_j < key_j1:
                    items[j], items[j + 1] = items[j + 1], items[j]
                    swapped = True
        if not swapped:
            break

    return items

to_items = lambda x: (x, your_dict[x])
key = lambda x: x[1]['age']
items = map(to_items, your_dict)
sorted_items = your_sorted(items, key = key)
sorted_dict = dict(sorted_items)
print('sorted_dict = {')
for k in sorted_dict:
    print('    ' + k + ': {')
    for kv in sorted_dict[k]:
        print('        ' + kv +': '+ str(sorted_dict[k][kv]) + ',')
    print('    }')
else:
    print('}')

运行程序:

python your_script.py
### 控制台就等于
# sorted_dict = {
#     ZhangHaoran: {
#         age: 22,
#         work: cs_representative,
#         salary: 4800,
#         qualify: high_school,
#         kpi: 0.78,
#     }
#     ZhouYawen: {
#         age: 26,
#         work: marketing_specialist,
#         salary: 8900,
#         qualify: undergraduate,
#         kpi: 0.82,
#     }
#     WangSiyuan: {
#         age: 28,
#         work: software_engineer,
#         salary: 18500,
#         qualify: master,
#         kpi: 1.12,
#     }
#     LiuZixuan: {
#         age: 29,
#         work: data_analyst,
#         salary: 16700,
#         qualify: undergraduate,
#         kpi: 0.89,
#     }
#     HuangYiming: {
#         age: 31,
#         work: oam_engineer,
#         salary: 15200,
#         qualify: college,
#         kpi: 1.03,
#     }
#     XuAnran: {
#         age: 33,
#         work: designer,
#         salary: 14500,
#         qualify: undergraduate,
#         kpi: 1.11,
#     }
#     LiYutong: {
#         age: 34,
#         work: sales_manager,
#         salary: 12600,
#         qualify: undergraduate,
#         kpi: 0.95,
#     }
#     ZhaoRuoxi: {
#         age: 37,
#         work: product_manager,
#         salary: 27500,
#         qualify: ,
#         kpi: 1.18,
#     }
#     WuZeyu: {
#         age: 41,
#         work: hr_specialist,
#         salary: 11200,
#         qualify: master,
#         kpi: 0.97,
#     }
#     ChenXiaomin: {
#         age: 45,
#         work: treasurer,
#         salary: 21800,
#         qualify: master,
#         kpi: 1.05,
#     }
# }

问题2

按月薪降序排序

解法

your_dict = {
    'WangSiyuan' : {
        'age': 28,
        'work': 'software_engineer',
        'salary': 18500,
        'qualify': 'master',
        'kpi': 1.12,
    },
    'LiYutong' : {
        'age': 34,
        'work': 'sales_manager',
        'salary': 12600,
        'qualify': 'undergraduate',
        'kpi': 0.95,
    },
    'ZhangHaoran' : {
        'age': 22,
        'work': 'cs_representative',
        'salary': 4800,
        'qualify': 'high_school',
        'kpi': 0.78,
    },
    'ChenXiaomin' : {
        'age': 45,
        'work': 'treasurer',
        'salary': 21800,
        'qualify': 'master',
        'kpi': 1.05,
    },
    'LiuZixuan' : {
        'age': 29,
        'work': 'data_analyst',
        'salary': 16700,
        'qualify': 'undergraduate',
        'kpi': 0.89,
    },
    'ZhaoRuoxi' : {
        'age': 37,
        'work': 'product_manager',
        'salary': 27500,
        'qualify': '',
        'kpi': 1.18,
    },
    'HuangYiming' : {
        'age': 31,
        'work': 'oam_engineer',
        'salary': 15200,
        'qualify': 'college',
        'kpi': 1.03,
    },
    'ZhouYawen' : {
        'age': 26,
        'work': 'marketing_specialist',
        'salary': 8900,
        'qualify': 'undergraduate',
        'kpi': 0.82,
    },
    'WuZeyu' : {
        'age': 41,
        'work': 'hr_specialist',
        'salary': 11200,
        'qualify': 'master',
        'kpi': 0.97,
    },
    'XuAnran' : {
        'age': 33,
        'work': 'designer',
        'salary': 14500,
        'qualify': 'undergraduate',
        'kpi': 1.11,
    },
}

def your_sorted(iterable, key = lambda x: x, reverse = False):
    items = list(iterable)
    n = len(items)

    for i in range(n - 1):
        swapped = False
        for j in range(n - 1 - i):
            key_j = key(items[j])
            key_j1 = key(items[j + 1])
            if not reverse:
                if key_j > key_j1:
                    items[j], items[j + 1] = items[j + 1], items[j]
                    swapped = True
            else:
                if key_j < key_j1:
                    items[j], items[j + 1] = items[j + 1], items[j]
                    swapped = True
        if not swapped:
            break

    return items

to_items = lambda x: (x, your_dict[x])
key = lambda x: x[1]['salary']
items = map(to_items, your_dict)
sorted_items = your_sorted(items, key = key, reverse = True)
sorted_dict = dict(sorted_items)
print('sorted_dict = {')
for k in sorted_dict:
    print('    ' + k + ': {')
    for kv in sorted_dict[k]:
        print('        ' + kv +': '+ str(sorted_dict[k][kv]) + ',')
    print('    }')
else:
    print('}')

运行程序:

python your_script.py
### 控制台将打印
# sorted_dict = {
#     ZhaoRuoxi: {
#         age: 37,
#         work: product_manager,
#         salary: 27500,
#         qualify: ,
#         kpi: 1.18,
#     }
#     ChenXiaomin: {
#         age: 45,
#         work: treasurer,
#         salary: 21800,
#         qualify: master,
#         kpi: 1.05,
#     }
#     WangSiyuan: {
#         age: 28,
#         work: software_engineer,
#         salary: 18500,
#         qualify: master,
#         kpi: 1.12,
#     }
#      LiuZixuan: {
#         age: 29,
#         work: data_analyst,
#         salary: 16700,
#         qualify: undergraduate,
#         kpi: 0.89,
#     }
#     HuangYiming: {
#         age: 31,
#         work: oam_engineer,
#         salary: 15200,
#         qualify: college,
#         kpi: 1.03,
#     }
#     XuAnran: {
#         age: 33,
#         work: designer,
#         salary: 14500,
#         qualify: undergraduate,
#         kpi: 1.11,
#     }
#     LiYutong: {
#         age: 34,
#         work: sales_manager,
#         salary: 12600,
#         qualify: undergraduate,
#         kpi: 0.95,
#     }
#     WuZeyu: {
#         age: 41,
#         work: hr_specialist,
#         salary: 11200,
#         qualify: master,
#         kpi: 0.97,
#     }
#     ZhouYawen: {
#         age: 26,
#         work: marketing_specialist,
#         salary: 8900,
#         qualify: undergraduate,
#         kpi: 0.82,
#     }
#     ZhangHaoran: {
#         age: 22,
#         work: cs_representative,
#         salary: 4800,
#         qualify: high_school,
#         kpi: 0.78,
#     }
# }

Python 内置了 sorted() 函数处理这类问题,以上解法可简化为:

your_dict = {
    'WangSiyuan' : {
        'age': 28,
        'work': 'software_engineer',
        'salary': 18500,
        'qualify': 'master',
        'kpi': 1.12,
    },
    'LiYutong' : {
        'age': 34,
        'work': 'sales_manager',
        'salary': 12600,
        'qualify': 'undergraduate',
        'kpi': 0.95,
    },
    'ZhangHaoran' : {
        'age': 22,
        'work': 'cs_representative',
        'salary': 4800,
        'qualify': 'high_school',
        'kpi': 0.78,
    },
    'ChenXiaomin' : {
        'age': 45,
        'work': 'treasurer',
        'salary': 21800,
        'qualify': 'master',
        'kpi': 1.05,
    },
    'LiuZixuan' : {
        'age': 29,
        'work': 'data_analyst',
        'salary': 16700,
        'qualify': 'undergraduate',
        'kpi': 0.89,
    },
    'ZhaoRuoxi' : {
        'age': 37,
        'work': 'product_manager',
        'salary': 27500,
        'qualify': '',
        'kpi': 1.18,
    },
    'HuangYiming' : {
        'age': 31,
        'work': 'oam_engineer',
        'salary': 15200,
        'qualify': 'college',
        'kpi': 1.03,
    },
    'ZhouYawen' : {
        'age': 26,
        'work': 'marketing_specialist',
        'salary': 8900,
        'qualify': 'undergraduate',
        'kpi': 0.82,
    },
    'WuZeyu' : {
        'age': 41,
        'work': 'hr_specialist',
        'salary': 11200,
        'qualify': 'master',
        'kpi': 0.97,
    },
    'XuAnran' : {
        'age': 33,
        'work': 'designer',
        'salary': 14500,
        'qualify': 'undergraduate',
        'kpi': 1.11,
    },
}

to_items = lambda x: (x, your_dict[x])

items1 = map(to_items, your_dict)
key1 = lambda x: x[1]['age']
sorted_items1 = sorted(items1, key = key1)
sorted_dict1 = dict(sorted_items1)

items2 = map(to_items, your_dict)
key2 = lambda x: x[1]['salary']
sorted_items2 = sorted(items2, key = key2, reverse = True)
sorted_dict2 = dict(sorted_items2)

print('sorted_dict1 = {')
for k in sorted_dict1:
    print('    ' + k + ': {')
    for kv in sorted_dict1[k]:
        print('        ' + kv +': '+ str(sorted_dict1[k][kv]) + ',')
    print('    }')
else:
    print('}')

print('|------------')

print('sorted_dict2 = {')
for k in sorted_dict2:
    print('    ' + k + ': {')
    for kv in sorted_dict2[k]:
        print('        ' + kv +': '+ str(sorted_dict2[k][kv]) + ',')
    print('    }')
else:
    print('}')

运行程序:

python your_script.py
### 控制台将打印
# sorted_dict1 = {
#     ZhangHaoran: {
#         age: 22,
#         work: cs_representative,
#         salary: 4800,
#         qualify: high_school,
#         kpi: 0.78,
#     }
#     ZhouYawen: {
#         age: 26,
#         work: marketing_specialist,
#         salary: 8900,
#         qualify: undergraduate,
#         kpi: 0.82,
#     }
#     WangSiyuan: {
#         age: 28,
#         work: software_engineer,
#         salary: 18500,
#         qualify: master,
#         kpi: 1.12,
#     }
#     LiuZixuan: {
#         age: 29,
#         work: data_analyst,
#         salary: 16700,
#         qualify: undergraduate,
#         kpi: 0.89,
#     }
#     HuangYiming: {
#         age: 31,
#         work: oam_engineer,
#         salary: 15200,
#         qualify: college,
#         kpi: 1.03,
#     }
#     XuAnran: {
#         age: 33,
#         work: designer,
#         salary: 14500,
#         qualify: undergraduate,
#         kpi: 1.11,
#     }
#     LiYutong: {
#         age: 34,
#         work: sales_manager,
#         salary: 12600,
#         qualify: undergraduate,
#         kpi: 0.95,
#     }
#     ZhaoRuoxi: {
#         age: 37,
#         work: product_manager,
#         salary: 27500,
#         qualify: ,
#         kpi: 1.18,
#     }
#     WuZeyu: {
#         age: 41,
#         work: hr_specialist,
#         salary: 11200,
#         qualify: master,
#         kpi: 0.97,
#     }
#     ChenXiaomin: {
#         age: 45,
#         work: treasurer,
#         salary: 21800,
#         qualify: master,
#         kpi: 1.05,
#     }
# }
# |------------
# sorted_dict2 = {
#     ZhaoRuoxi: {
#         age: 37,
#         work: product_manager,
#         salary: 27500,
#         qualify: ,
#         kpi: 1.18,
#     }
#     ChenXiaomin: {
#         age: 45,
#         work: treasurer,
#         salary: 21800,
#         qualify: master,
#         kpi: 1.05,
#     }
#     WangSiyuan: {
#         age: 28,
#         work: software_engineer,
#         salary: 18500,
#         qualify: master,
#         kpi: 1.12,
#     }
#     LiuZixuan: {
#         age: 29,
#         work: data_analyst,
#         salary: 16700,
#         qualify: undergraduate,
#         kpi: 0.89,
#     }
#     HuangYiming: {
#         age: 31,
#         work: oam_engineer,
#         salary: 15200,
#         qualify: college,
#         kpi: 1.03,
#     }
#     XuAnran: {
#         age: 33,
#         work: designer,
#         salary: 14500,
#         qualify: undergraduate,
#         kpi: 1.11,
#     }
#     LiYutong: {
#         age: 34,
#         work: sales_manager,
#         salary: 12600,
#         qualify: undergraduate,
#         kpi: 0.95,
#     }
#     WuZeyu: {
#         age: 41,
#         work: hr_specialist,
#         salary: 11200,
#         qualify: master,
#         kpi: 0.97,
#     }
#     ZhouYawen: {
#         age: 26,
#         work: marketing_specialist,
#         salary: 8900,
#         qualify: undergraduate,
#         kpi: 0.82,
#     }
#     ZhangHaoran: {
#         age: 22,
#         work: cs_representative,
#         salary: 4800,
#         qualify: high_school,
#         kpi: 0.78,
#     }
# }

  1. 在学习到保留字部分之前,目前你不需要知道有哪些Python保留字 ↩︎

  2. 不是强制性的,事实上不按照惯例来命名变量,并不会有错误,但是会引起同行的不满 ↩︎

  3. 类型注释在 Python 3.5 之前的版本是没有的,它正式引入于 Python 3.5,此后的版本均可以使用 ↩︎

  4. 不是必须看的部分,跳过也可以 ↩︎

  5. Pylance插件深度集成了Pyright,Pyright是用TypeScript编写的静态类型检查工具,不过,Pylance的功能不仅限于此,后面将有对Pylance更多的应用 ↩︎

  6. 因为bool实际上是int的子类 ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  7. and运算与or运算返回原值,非bool数据类型的数据,这与not运算不同,这被称为短路求值 ↩︎ ↩︎ ↩︎

  8. 事实上由于Python的应用领域不在底层,位运算符用得很少 ↩︎

  9. 负数的二进制以补码形式表示,位运算时会自动处理符号位 ↩︎

  10. 因为int数据类型的数据是由二进制数存储的 ↩︎ ↩︎

  11. 严格区分大小写 ↩︎ ↩︎ ↩︎ ↩︎

  12. 空字符串恒为True ↩︎

  13. 空字符串恒为False ↩︎

  14. 空比特串恒为True ↩︎

  15. 空比特串恒为False ↩︎

  16. 标准不明,空字符串、长度为1的ASCII字符一定会有字符串驻留的情况,长度为≤20的ASCII字符串、编译期确定的字符串字面量火满足标识符规则的字面量可能有字符串驻留的情况,动态生成的字符串、长度超过20个字符的ASCII字符串或含有特殊字符的字符串通常没有字符串驻留的情况,需要查询相关文档 ↩︎

  17. 标准完全不明,需要查询相关文档 ↩︎

  18. 以上提到的机制,以及其他某些机制,需要查询相关文档 ↩︎

  19. 即float数据类型 ↩︎

  20. 有有效的解决方法,在之后会讲到 ↩︎

  21. 这个部分可以跳过不看,除非你遇到需求 ↩︎

  22. 所有表达式都是代码语句 ↩︎

  23. 通常用四个空格缩进 ↩︎

  24. 指所有实现了iter()方法或getitem()方法的对象,str、list、tuple、dict、set、frozenset、range、bytes、bytearray都是可迭代的数据类型 ↩︎ ↩︎

  25. 而且 try、except 和 finally 要求缩进相同,这又有些 类似条件语句。 ↩︎

  26. 你的这段代码可能有其他异常,但是你的这段代码有其他异常又不太可能 ↩︎

  27. 可以跳过,等需要了再来看 ↩︎

  28. 如果用不到,可以以后再来看 ↩︎

  29. 递归版本的求斐波那契数的时间复杂度为O(2ⁿ),迭代版本的求斐波那契数的时间复杂度为O(n),如果n=10000,迭代版本只需在10 ^4步内返回值,而递归版本则要在约10^3010.30步内才能返回值 ↩︎

  30. 概念较难理解,可以等需要时再看 ↩︎

  31. 严格来说,若该局部变量是list、dict、set、bytearray等可变的数据类型的数据,是可以修改其内部元素的值的,并非严格只读 ↩︎ ↩︎

  32. 不仅有函数,不过这是后话了 ↩︎

  33. 如 PyCharm、VS Code 等 ↩︎

  34. 详见逻辑运算符 ↩︎

  35. Python 3.7+ 版本中,dict 数据类型的数据实际上是有序的(按插入顺序存储) ↩︎

  36. dict 数据类型的数据的键必须是不可变数据类型的数据 ↩︎

  37. 这里不要求是正的 int 数据类型的数据 ↩︎

  38. [0, 255]中的0 ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值