Python | 第八章 | 数据容器
P70 函数的传参机制 2024/9/1
一、传参机制
函数的传参机制对我们今后的编程非常重要,一定要搞的清清楚楚明明白白,我们通过案例来学习
- 字符串和数值类型的传参机制
- 1)看一个案例,分析结果是什么?[可以得出函数中开辟新栈并不会影响主栈]
# @Author :zjc
# @File :05_string_num_type_convey.py
# Time :2024/9/1 17:17# 定义函数f1
def f1(a):print(f"f1() a的值:{a}地址是:{id(a)}")a += 1print(f"f1() a的值:{a}地址是:{id(a)}")# 定义变量a = 10
a= 10
print(f"调用f1()前a的值: {a}地址是:{id(a)}")
f1(a)
print(f"调用f1()后a的值:{a}地址是:{id(a)}")
- 内存分析图:
- 字符串同样如此:
# 定义函数F2
def f2(name):print(f"F2() name的值: {name}地址是:{id(name)}")name += " hi"print(f"f2() name的值: {name}地址是:{id(name)}")print("-------------")
name = "tom"
print(f"调用f2()前name的值:{name}地址是:{id(name)}")
f2(name)
print(f"调用f2()后name的值: {name}地址是: {id(name)}")
二、结论
-
字符串和数值类型是不可变数据类型[值改变,地址改变],当对应的变量的值发生了变化时,它对应的内存地址会发生改变。
-
可变数据类型就是:值改变,而地址不改变。
P71 递归机制 2024/9/2
一、基本介绍
1、简单的说:递归就是函数自己调用自己,每次调用时传入不同的值
2、递归有助于编程者解决复杂问题,同时可以让代码变得简洁
- 递归解决的案例:
- 递归举例1:
# 当执行test(4),输出什么?
def test(n):if n > 2:# test(n - 1)print("n=", n) # 执行test(4)
内存图:
递归举例2:
- 阶层问题…
# 阶乘,当执行factorial(4),返回值是多少?
def factorial(n):if n == 1:return 1else:return factorial(n - 1) * n
# 执行
print(factorial(4))
- 内存图:
二、递归重要规则
-
执行一个函数时,就创建一个新的空间(栈空间)
-
函数的变量是独立的,比如n变量
-
递归必须向退出递归的条件逼近,否则就是无限递归,就会出现
RecursionError: maximum recursiondepth exceeded
【死龟了:)】 -
当一个函数执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁。
P72 递归练习 2024/9/2
一、题目练习
- 请使用递归的方式求出斐波那契数1,1,2,3,5,8,13…给你一个整数n,求出它的值是多。
# @Author :zjc
# @File :07_function_recursion_exercise.py
# Time :2024/9/2 16:12# 请使用递归的方式求出斐波那契数1,1,2,3,5,8,13...给你一个整数n,求出它的值是多。
def fbn(n):"""功能:返回n对应的斐波那契数:param n:接受一个整数 n => 1:return:返回裴波那契数"""if n == 1 or n == 2:return 1# 如果n>2则对应的裴波那契数为 n-1和n-2对应的裴波那契数的和else:return fbn(n - 1) + fbn(n - 2)# 完成测试
print(fbn(3))
- 猴子吃桃子问题:有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!以后每天猴子都吃其中的一半,然后再多吃一个。当到第10天时,想再吃时(即还没吃),发现只有1个桃子了。问题:最初共多少个桃子?
# @Author :zjc
# @File :08_recursion_exercise_02.py
# Time :2024/9/2 16:26"""思路分析:1. day ==10 ,桃子数 = 12. day == 9 桃子数 == (day10+1)*23. day == 8 桃子数 == (day9+1)*24. day == 7 桃子数 == (day8+1)*2
"""# 定义函数,返回对应天数的桃子
def peach(day):if day == 10:return 1# 如果是 1<=day<10的范围就是从后一天开始推导else:return (peach(day + 1) + 1) * 2# 完成测试
print("最初桃子数为:", peach(1))
- 求函数值,已知 f(1)= 3; f(n)= 2 * f(n-1)+1;请使用递归的思想编程,求出 f(n)的值?
# @Author :zjc
# @File :09_recursion_exercise03.py
# Time :2024/9/2 17:50# 求函数值,已知 f(1)= 3; f(n)= 2 * f(n-1)+1;请使用递归的思想编程,求出 f(n)的值?"""思路分析:1. 已经给出了推推导公式2. 我们将其翻译成对应的代码即可
"""def f(n):if n == 1:return 3else:return 2 * f(n - 1) + 1
print(f(10))
二、递归调用实例-汉诺塔
- 代码演示:
# @Author :zjc
# @File :010_hannoi_tower.py
# Time :2024/9/2 17:55def hanoi_tower(num, a, b, c):"""输出num个盘子移动的顺序:param num: 指定盘子数:param a: 表示A柱子:param b: 表示B柱子:param c: 表示C柱子:return:"""# 如果只有一个盘if num == 1:print("第1个盘从:", a, "->", c)else:# 1. 有多个盘,我们认为只有两个,上面所有的盘和最下面的盘# 2. 移动上面所有的盘到B柱子,这个过程会借助到C柱子hanoi_tower(num - 1, a, c, b)# 移动最下面的盘print(f"第{num}个盘从: {a} -> {c}")# 1.把B柱子所有的盘移动到C柱子,这个过程会使用到A柱子hanoi_tower(num - 1, b, a, c)# 测试一把
hanoi_tower(3, "A", "B", "C")
P73 函数作为参数传递 2024/9/3
一、实际需求
- 请看下面代码:
# 看一个实际需求
def f1(num1, num2):max_val = num1 if num1 > num2 else num2return max_valdef f2(num1, num2):max_val = num1 if num1 > num2 else num2return num1 + num, max_val
问题分析:
1.f1和f2都有对两个数求最大值的需求
2.如果有更多的函数也有对两个数求最大的需求,在每个函数都写一份相同代码会冗余,而且,不利用维护
3.解决方案,编写一个函数(该函数可以返回两个数的最大值),
4.将该函数作为参数传给f1,f2就非常棒了->引出知识点:将函数作为参数传递
- 通过函数改进:
# @Author :zjc
# @File :012_function_arg.py
# Time :2024/9/3 14:11# 定义一个函数,可以返回两十数的最大值
def get_max_val(num1, num2):max_val = num1 if num1 > num2 else num2return max_valdef f1(fun, num1, num2):"""# 调用fun返回num1和num2的最大值:param fun: 表示接收一个函数:param num1: 传入数:param num2: 传入数:return: 最大值"""return fun(num1, num2)def f2(fun, num1, num2):"""功能:调用fun返回num1和num2的最大值,同时返回两个数的和:param fun::param num1::param num2::return:"""return num1 + num2, fun(num1, num2)# 测试
print(f1(get_max_val, 50, 20))
x, y = f2(get_max_val, 10, 50)
print(f"x = {x},y = {y}")
二、注意事项和细节
注意事项和细节:
1、函数作为参数传递,传递的不是数据,而是业务处理逻辑
2、一个函数,可以接收多个函数作为参数传入
# @Author :zjc
# @File :013_function_arg_02.py
# Time :2024/9/3 14:36# f3接收多个函数作为参数传入
def f3(my_fun, num1, num2, my_fun2):return my_fun(num1, num2), my_fun2(num1, num2)# 定义一个函数,可以返回两个数的最大值
def get_max_val(num1, num2):max_val = num1 if num1 > num2 else num2return max_val# 定义函数,可以返回两个数的和
def get_sum(num1, num2):return num1 + num2# 演示
x, y = f3(get_max_val, 10, 20, get_sum)
print(f"x = {x},y = {y}")
P74 lambda匿名函数 2024/9/3
一、基本介绍
需求:如果我们有这样一个需求,需要将函数作为参数进行传递,但是这个函数只使用一次,这时,我们可以考虑使用lambda匿名函数。
-
1、函数的定义
-
def关键字,可以定义带有名称的函数,可以重复使用
-
lambda关键字,可以定义匿名函数(无名称),匿名函数只能使用一次
-
匿名函数用于临时创建一个函数,只使用一次的场景
-
-
2、匿名函数基本语法
-
lambda形参列表:函数体(一行代码)
-
lambda关键字,表示定义匿名函数
-
形参列表:比如num1, num2表示接收两个参数
- 函数体:完成的功能,只能写一行,不能写多行代码
-
-
应用实例:
编写一个函数,可以接收一个匿名函数和两个数,通过匿名函数计算,返回两个数的最大值
# @Author :zjc
# @File :014_anno_function.py
# Time :2024/9/3 19:51# 思路分析+代码实现
# 编写一个函数,可以接收一个匿名函数和两个数,通过匿名函数计算,
#返回两个数的最大值def f1(fun, num1, num2):"""功能:调用fun返回num1和num2的最大值:param fun:接收函数(匿名的):param num1::param num2 ::return:"""# 确认接受到的类型print(f"fun类型: {type(fun)}")return fun(num1, num2)# 关键是看如何传入匿名函数调用"""1.lambda a, b: a if a > b else b 就是匿名函数2. 不需要return,运算结果就是返回值
"""
# 无需def定义和起名,更加简洁方便
max_val = f1(lambda a, b: a if a > b else b, 12, 10)
print("最大值 = ",max_val)
P75 变量的作用范围 2024/9/4
一、基本介绍
全局变量: 在整个程序范围内都可以访问,定义在函数外,拥有全局作用域的变量。
局部变量: 只能在其被声明的函数范围内访问,定义在函数内部,拥有局部作用域的变量
- 案例演示:
# @Author :zjc
# @File :015_globe_part_var.py
# Time :2024/9/4 16:44# n1就是全局变量
n1 = 100def f1():# n2就是局部变量n2 = 200print(n2)# 可以访问全局变量n1print(n1)f1()
print(n1)# 不能访问局部变量n2
print(n2)
二、注意事项和细节
- 未在函数内部重新定义n1,那么默认使用全局变量n1
n1 = 100
def f1():print(n1)f1()
- 在函数内部重新定义了n1,那么根据就近原则,使用的就是函数内部重新定义的n1
n1= 100def f1():# n1重新定义了n1 = 200print(n1)f1()
print(n1)
- 在函数内部使用global关键字,可以标明指定使用全局变量
n= 100def f1():# global关键字标明使用全局变量n1global n1n1 = 200print(n1)f1()
# n1 的值会发生变化
print(n1)
三、知识点总结
P76 数据容器的概述 2024/9/4
一、问题引出
- round函数:
# @Author :zjc
# @File :01_hens_questions.py
# Time :2024/9/4 18:04hen1 = 3
hen2 = 5
hen3 = 1
hen4 = 3.4
hen5 = 2
hen6 = 50
# 计算总的体重
total_weight = hen1 + hen2 + hen3 + hen4 + hen5 + hen6 # 计算平均体重
avg_weight = total_weight / 6
print(f"总体重:{total_weight}平均体重: {round(avg_weight, 2)}") # 保留小数点后两位
- 问题分析:
1.如果不是6只鸡而是600只鸡怎么办?
2.不利于代码的维护,不灵活
3.引出—>列表知识点
二、基本介绍
1、数据容器是一种数据类型,有些地方也简称为容器/collections
2、数据容器可以存放多个数据,每一个数据也被称为一个元素
3、存放的数据/元素可以是任意类型
4、简单的说,数据容器就是I种可以存放多个数据/元素的数据类型
5、简单的示意图
P77 列表基本使用 2024/9/5
参考文档:https://docs.python.org/zh-cn/3.11/library/stdtypes.html#list
一、基本介绍
1、列表可以存放多个不同类型数据,即:列表就是一列数据(多个数据)
2、列表也是一种数据类型
- 列表的定义:
- 创建一个列表,只要用逗号分隔的不同的数据项使用方括号括起来即可,示例如下:
list1 = [1, 2, 3, 4, 5, 6, 7, 8]
list2 = ["tom", "jack", "mary", "blue"]
- 说明:为了让大家明白,我画列表内存图说明
比如:你要使用list2列表的第3个值"blue",则通过 list[2]就可以访问到
老韩提示:注意索引是从0开始计算的
- 代码演示:
# @Author :zjc
# @File :03_list_define.py
# Time :2024/9/5 19:11# 代码
# 列表的定义"""
老师解读
1.定义了一个列表,名字为list1,值为[100,200,300,400,500]
2.100就是其中的一个数据/元素
"""list1 = [100, 200, 300, 400, 500]
# 输出列表数据
print(list1) # 输出列表的素有数据项和元素项
print(type(list1))
print(list1[0])
二、列表的遍历
-
什么是列表的遍历:
简单的说,就是将列表的**每个元素依次取出,**进行处理的操作,就是遍历/迭代
-
list_color = ['red', 'green', 'blue', 'yellow', 'white','black']
-
使用while循环,对列表进行遍历输出:
len()
函数介绍:返回对象的长度
# @Author :zjc
# @File :05_list_while.py
# Time :2024/9/5 19:21# while 遍历列表
"""
思路分析
1.先定义变量index = 0 表示从第一个元素开始取出
2.决定列表list_color的个数6,这里其实有一个内置函数len(列表),可以返回个数
3.每取出一个就输出 / 或者根据自己的业务处理
"""
list_color = ['red', 'green', 'blue ', 'yellow', 'white ', 'black']# 得到list元素个数
print(len(list_color))# 1. while循环
index = 0
while index < len(list_color):print(f"第{index + 1}个元素", list_color[index])index += 1# 2. for循环
for ele in list_color:print("元素是", ele)
三、解决养鸡场问题
问题:一个养鸡场有6只鸡,它们的体重分别是3kg, 5kg, 1kg, 3.4kg, 2kg, 50kg。请问这六只鸡的总体重是多少?平均体重是多少?请你编写程序计算。
# 使用列表解决养鸡场问题
hens = [3, 5, 1, 3.4, 2, 50]
total_weight = 0.0
# 遍历 hens
for ele in hens:total_weight += eleprint(f"总体重:{total_weight}平均体重: {round(total_weight/len(hens), 2)}")
P78 列表使用细节(1)2024/9/6
一、注意事项和细节
1、如果我们需要一个空列表,可以通过**[]或者list()**方式来定义
官方文档:https://docs.python.org/zh-cn/3.11/library/stdtypes.html#list
# @Author :zjc
# @File :06_list_detail.py
# Time :2024/9/6 15:24# 列表使用注意事项和细节
# 1.如果我们需要一个空列表,可以通过,或者list()方式来定义
list1 =[]
list2 = list()
print(list1, type(list1))
print(list2, type(list2))
2、列表的元素可以有多个,而且数据类型没有限制,允许有重复元素,并且是有序的
# 2、列表的数据项可以有多个,而且数据类型没有限制
list3 = [100, "jack", 4.5, "jack"]
print(list3)
# 嵌套列表
list4 = [100, "tom", ["天龙八部", "笑傲江湖", 300]]
print("list4=", list4)
3、列表的索引/下标是从0开始的
4、列表索引必须在指定范围内使用,否则报: IndexError: list index out of range
,比如 list1 =[1,2.1,'韩顺平教育']
有效下标为0-2
list5 = [1, 2.1, "韩顺平教育"]
# 索引越界
print(list5[3])
5、索引也可以从尾部开始,最后一个元素的索引为-1,往前一位为-2,以此类推
list1 = [1, 2.1, '韩顺平教育']
print(list1[-1])
print(list1[-2]) # 依然不能索引越界
# print(list1[-4])
6、通过列表[索引]=新值对数据进行更新,使用列表.append(值)方法来添加元素,使用del语句来删除列表的元素,注意不能超出有效索引范围
s.append(x) | 将 x 添加到序列的末尾 (等同于 s[len(s):len(s)] = [x] ) |
---|
# 通过列表[索引]=新值对数据进行更新,使用列表.append(值)方法来添加元素
list_a = ["天龙八部", "笑傲江湖"]
print("list_a: ", list_a)
list_a[0] = "雪山飞狐"
print("list_a: ", list_a)# 在“笑傲江湖”后面增加元素“倚天屠龙”
list_a.append("倚天屠龙")
print("list_a: ", list_a)# 删除索引为1的元素 ”笑傲江湖“
del list_a[1]
print("list_a->", list_a)
P79 列表使用细节(2)2024/9/6
一、注意事项和细节
7、列表是可变序列(要注意其使用特点),看下面的代码
# 列表的元素是可以修改的,修改后,列表变量指向地址不变,只是数据内容变化
list1 = [1, 2.1, '韩顺平教育']
print(f"list: {list1}地址: {id(list1)},|| {list1[2]} {id(list1[2])}")
# 修改之后元素的地址会发生改变
list1[2] = 'python'# 可以修改
print(f"list: {list1}地址: {id(list1)},|| {list1[2]} {id(list1[2])}")
# 扩展一下,列表在赋值的特点,示意图说明
list1 = [1, 2.1, '韩顺平教育']
list2 = list1
- 内存示意图:
- 代码分析1:
# 扩展一下,列表在赋值的特点,示意图说明
list1 = [1, 2.1, '韩顺平教育']
list2 = list1
# 修改会影响list1的值
list2[0] = 500
print("list2=", list2) # 输出?
print("list1=", list1) # 输出?
- 示意图:
- 代码分析2:
def f1(l):l[0] = 100 # 函数中会改变list10元素[0]的值print("l的id: ", id(l))list10 = [1, 2.1, 200]
print("list10的id: ", id(list10))
f1(list10) # 实际上传递的是地址
print("list10: ", list10) # 输出?
- 示意图: