1、不定长参数
- 位置不定长参数(*args):多余的位置参数,可以被args接收,并且打包为一个元组,保存在args当中。
# 不定长参数主要就是在定义函数时,不确定参数的个数时即可进行不定长参数的书写 ''' 位置不定长参数的定义格式: def 参数名(*args): 函数体 ''' # def func(*args): # print(*args) # 相当于书写内容为 print(1,2,3) # # # func(1, 2, 3) # print(1, 2, 3) # args变量到底内部是什么样子的? # def func(*args): # return args # 数据传入函数内部时,将传入的多个数据进行打包,转换为一个元组,被args接收.并且在函数体内部可以使用该元组参与运算 # print(func(1, 2, 3)) # (1, 2, 3) # 案例: # 输入不确定数量的多个值,判断其中的最大值 def max1(*args): max_num = args[0] # 如果max_num = 0 这个时候我们所有值都没负的时候会判断出错 for i in args: if i > max_num: max_num = i return max_num print(max1(1, 4, 5, 3, 6, 12, 3)) # 如果输入的数值全部为负呢? print(max1(-1, -2, -5))
- 关键字不定长参数(**kwargs):将多余的关键字 参数,打包为一个字典,保存在kwargs当中
# 关键字不定长参数,可以接收多个未定义参数的关键字赋值 ''' 关键字不定长参数的格式: def 函数名(**kwargs): 函数体 ''' # TypeError: 'a' is an invalid keyword argument for print() # def func(**kwargs): # print(**kwargs) # 相当于给print输入了多个关键字参数 print(a=1, b=2, c=3) # # # func(a=1, b=2, c=3) # 使用**kwargs可以将关键字参数进行传递 # def func(**kwargs): # print(1, 2, **kwargs) # 相当于print(1, 2, sep='&', end='a') # # # func(sep='&', end='a') # kwargs 内部到底是什么存储结构呢? def func(**kwargs): # kwargs 在从传参之后,会将实参位置的所有未定义参数的关键字参数转换为字典的键值对,保存在kwargs当中 print(kwargs) # {'a': 1, 'b': 2, 'c': 3} func(a=1, b=2, c=3) # 案例: # 使用创建一个函数可以保存学员的全部信息,并将其储存到字典当中 def student_info(**kwargs): print(f'学员信息为:{kwargs}') student_info(name='xiaoming', age=18, gender='男')
2、函数定义和调用时各类参数的排布顺序
- 形参: 位置参数>>位置不定长参数>>缺省参数>>关键字不定长参数
- 实参:顺序赋值>>关键字参数赋值
- 在开发中除非有特殊需求,一般参数种类不超过三种,参数个数不超过5个,如果种类或数量太多,会造成我们开发中沟通成本加大
# 在定义函数时:位置参数,缺省参数,位置不定长参数,关键字不定长参数 到底在定义时怎么排列呢? # 调用函数时:顺序赋值, 关键字赋值 调用时的传参顺序是什么样的呢? # 定义函数时:形参 # 位置参数和缺省参数的位置关系: # def func1(a, b, c=10): # print(a) # print(b) # print(c) # 缺省参数c 能否放到a,b之前或之间 # SyntaxError: non-default argument follows default argument # 有默认值的参数只能放到没有默认值的参数之后,不能前置 # def func1(c=10,a, b ): # print(a) # print(b) # print(c) # # # 赋值时可以不给c传参因为其有默认值 # func1(1, 2) # 结论: 在定义函数时,位置参数在缺省参数之前 # 位置参数,缺省参数,位置不定长参数之间的位置关系 # 顺序赋值多个参数,位置参数优先接收,然后缺省参数接收数据,多余的参数被args以元组形式打包接收 # 思考:为什么要设置缺省参数呢? 一般情况下,缺省参数是不进行赋值的,因为绝大多数情况下都会赋默认值,极少情况下会使用关键字参数赋值 # 如果放到*args之前,是不是每次给*args赋值,都要给缺省参数赋值,所以不是很方便 # 综上考虑,我们决定将缺省参数放到*args之后 # def func2(a, b, c=10, *args): # print(a) # print(b) # print(c) # print(args) # 传值逻辑如下:1.先给位置参数赋值 2.多余的未接收数据,被args打包为一个元组进行接收 3.缺省参数一般情况下不赋值,如果需要赋值,使用关键字参数赋值 # 在官方文档或者系统模块中,都是这种顺序书写的 # def func2(a, b, *args, c=10): # print(a) # print(b) # print(c) # print(args) # # # func2(1, 2, 3, 4, 5) # 结论:在定义函数时,先写位置参数,再写位置不定长参数,最后写缺省参数 # 位置参数,缺省参数,位置不定长参数,关键字不定长参数之间的位置关系 # def func2(a, b, *args, c=10, **kwargs): # print(a) # print(b) # print(c) # print(args) # print(kwargs) # # # func2(1, 23, 4, 5, 3, 2, c=1, name='xiaoming', age=18) # 思考:**kwargs可不可以往前放 # **kwargs只能放到最后,否则会报错 # 结论:形参排布顺序为:位置参数>>位置不定长参数>>缺省参数>>关键字不定长参数 # 调用函数时:实参 def sum1(a, b): print(a + b) # SyntaxError: positional argument follows keyword argument # 顺序赋值,不能在关键字赋值之后 # sum1(a=1, 2) # 结论,调用参数时,先使用顺序赋值,后使用关键字赋值
3、组包和拆包
- 组包:将多个数据,组合为一个容器类型,进行使用或变量保存
- 拆包:将一个容器类型,进行拆分,其中的每一个元组赋值给其他的变量
# 组包:就是讲多个值进行组合,打包为一个容器类型的过程 # 拆包:就是讲一个容器类型,拆分成多个数据,分别赋值给多个变量的过程 # 组包 def func1(): return 1, 2, 3, 4 # func1返回了多个数据,Python自动将其打包为一个元组,这个过程就是组包 print(func1()) # (1, 2, 3, 4) # 将多个数据打包整合为一个容器,赋值给变量,这个就是组包过程 a = 1, 2, 3, 4 print(a) # 拆包(解包) # 将等号右侧的列表,拆分为四个数据元素,分别赋值给a,b,c,d这个过程就是拆包 a, b, c, d = [1, 2, 3, 4] print(a, b, c, d) # 之前我们在循环汇总用过拆包过程 list1 = [1, 2, 3, 4] for index, value in enumerate(list1): print(index, value) dict1 = {'name': 'xiaoming', 'age': 18} for key, value in dict1.items(): print(key, value) # 同时应用组包和拆包的案例 a = 1 b = 2 # 需求:将a, b进行互换值 # 这个互换过程,是先讲a,b的值提取出来,组包为一个元组,然后进行拆包,将元组内的两个数据分别赋值给,a,b变量 a, b = b, a print(a, b) # 注意:字典可以拆包么?可以 dict1 = {'a': 1, 'b': 2, 'c': 3} # 对字典进行拆包,得到的是字典的键 char1, char2, char3 = dict1 print(char1, char2, char3) # 集合可以拆包么? 可以 # 对于集合进行拆包,没有任何问题,但是一般不会这样做,因为赋值顺序无法指定 set1 = {'Tom', 'Bob', 'Rose'} a, b, c = set1 print(a, b, c)
4、引用
- 数据的三个维度:值, 数据类型,唯一标识
- 值: 数据计算时使用的值
- 数据类型:数据的存储类型
- 唯一标识:id ,也就是数据的内存地址的标识
- 如果我们想要判断id 或者说唯一标识是否相等,我们使用is进行判断
# 在Python中所有的数据分为三个维度: 值(判断==), 数据类型(int...float...), 唯一标识(id) # 值相等的数据,唯一标识和数据类型不一定相等 bool1 = False int1 = 0 # 0 和False的值相等 print(bool1 == int1) # True # 数据类型不等 print(type(bool1) == type(int1)) # False # 唯一标识不等 # id 判断数据是否是同一内存空间中的数据(同一内存空间中的数据必相等) print(id(bool1) == id(int1)) # False # 值和数据类型相等的,唯一标识不一定相等 list1 = [1, 2, 3] list2 = [1, 2, 3] # list1 和list2 值相等 print(list1 == list2) # True # list1和list2 数据类型相等 print(type(list1) == type(list2)) # True # list1 和list2 的唯一标识不等,也就是说,其所在的内存空间不一致 print(id(list1) == id(list2)) # False # 唯一标识相等的, 值和数据类型必然相等 # 在同一内存空间中只能储存同一个值 str1 = 'abc' str2 = 'abc' # str1 和str2 的唯一标识相等 print(id(str1) == id(str2)) # True # 数据类型相等 print(type(str1) == type(str2)) # True # 数据值相等 print(str1 == str2) # True # 怎样判断id 唯一标识是否相等呢? is关键字 # 使用is可以判断是否为同一个内存空间中的数据 print(list1 is list2) # False print(str1 is str2) # True
5、可变类型和不可变类型
- 可变类型:内存空间中的数据可以被修改的数据类型
- list set dict
- 不可变数据类型:内存空间中的数据不可以被修改的数据类型
- int float bool str tuple
# 传参或者变量的传递是进行了值的传递 还是进行了引用地址的传递呢? list1 = [1, 2, 3, 4] list2 = [1, 2, 3, 4] # id的值,我们称呼他为引用地址,list1 和list2 的引用地址不相同 print(id(list1)) # 140652966394496 print(id(list2)) # 140652968507776 # 如果我向list1 中添加以数字,次数list2 中的值为多少? [1,2,3,4] list1.append(5) print(list1) print(list2) # 在list1中添加了数据,那list1的引用地址会发生变化么? 不会变化 # list1,在使用append方法时,是将内存空间中的值进行了变化,没有修改内存地址,所以id值不变 print(id(list1)) # 140652966394496 # 如果我们使用list1 = list2,list1的引用地址发生变化了么? 变化 list1 = list2 # 在对list1赋值时,list2 将list1中的内存地址赋值给了list1,此时,list1和list2指向同一块内存空间 print(id(list1)) # 140652968507776 # 如果在list1中删除下标为1的元素,list2 会发生变化么? 会变化 # list1 和list2 指向同一块内存空间,所以内存空间中的数据发生了改变,list1 和list2 均发生了变化 list1.pop(1) print(list1) # [1, 3, 4] print(list2) # [1, 3, 4] # list 内存空间中的数据可以发生改变,这种数据类型我们称之为可变数据类型 # 练习: list1 = [1, 2, 3, 4] list2 = [1, 2, 3, 4] list1 = [1, 2, 3, 4, 5] list2 = list1 list1.pop(2) list1 + list2 # list1 和list2 分别是多少 print(list1) print(list2) # 练习: str1 = '12345' str2 = '12345' str1 = str2 str1 = '123' str2 + str1 print(str1) print(str2) str1 = '123' str2 = '123' print(id(str1)) # 140509725727984 print(id(str2)) # 140509725727984 # 我么可以修改str内部的元素么? 不可以 # TypeError: 'str' object does not support item assignment # str1[0] = '2' # 既然内部元素不可修改,系统定义其值相同时,数据引用地址也相同 # 我么称这种内存空间中的数据无法被修改的值为不可变数据类型 # 结论: # 可变数据类型: 列表,集合,字典 # 不可变数据类型: 字符串,元组,整型,浮点型,布尔型 # 结论:在数据的传递过程中,是引用传递,不是值的传递
6、引用当做参数传递
- 在函数传参过程中,变量会以引用的形式进行传参,也就是说我们的变量或参数传递是引用传递,不是值传递
- 如果参数是可变数据类型,那么在函数内修改其引用地址指向空间内的数据,外部数据同时发生变化
- 如果参数是不可变数据类型,其实也是引用传递,只不过引用地址指向的数据空间中的数据无法被修改
# 将数字1所在空间的引用地址赋值给了a # a = 1 # 将a所保存的引用你地址给了b # b = a def func(num_list): print(id(num_list)) # 140490414045760 num_list.append(2) return num_list # 在进行参数传递时,是进行了地址传递,将list1 的引用地址传递给了num_list,num_list 修改内存空间中的数据时,list1,也会发生变化 list1 = [1, 2, 3, 4] # print(id(list1)) # 140490414045760 # print(func(list1)) # print(list1) def func2(num_list): print(id(num_list)) # 140326362233472 # 无论什么情况下,使用=赋值运算,就是讲等号右侧数据的引用地址,赋值给等号左侧的变量 num_list = [1, 2, 3, 4, 5] print(id(num_list)) # 140466340833664 return num_list print(id(list1)) # 140326362233472 print(func2(list1)) # [1, 2, 3, 4, 5] print(list1) # [1, 2, 3, 4] # 不可变数据类型 def func1(char1): print(id(char1)) # 140228523715632 # 不可变数据类型,在进行参数传递时,也是引用传递,但是他无法修改原有数据空间内的数据,如果想要修改只能改变引用地址,重新赋值(只有=才有改变引用的功能) char1.replace('a', 'b') return char1 str1 = 'abc' print(id(str1)) # 140228523715632 print(func1(str1)) # abc print(str1) # abc
7、学生管理系统
# 需求拆分: """ 1.展示学生管理系统的功能有哪些,引导用户键入序号选择功能 2.获取用户键入的功能 3.分析具体要执行哪一项功能 4.执行功能 """ def show_menu(): """用户功能界面展示""" print('*' * 50) print('欢迎使用学生管理系统') print('1.添加学生') print('2.删除学生') print('3.修改学生') print('4.查询学生') print('5.显示所有学生') print('6.退出系统') print('*' * 50) def get_num(): """引导用户输入功能序号,并获取序号""" num = input('请输入你用执行的功能序号:') return num def add_student(): """添加学生""" # 1.获取用户输入的学生信息 # 1.1 当用户输入的id值已经存在时,提示用户重新输入 stu_id = input('请输入学生ID:') while True: if stu_id in [stu['id'] for stu in students_list]: stu_id = input('该学生ID已经存在,请重新输入:') else: break name = input('请输入学生姓名:') # 1.2 当用户输入的年龄不是数字时,提示用户重新输入 age = input('请输入学生年龄(数字):') while True: if age.isdigit(): break else: age = input('输入的年龄不是数字,请重新输入:') # 1.3 当用户输入的手机号不是数字时或者输入的手机号已存在时,提示用户重新输入 mobile = input('请输入学生手机号:') while True: # 判断手机号是否是数字 if mobile.isdigit(): # 判断手机号是否为11位 while True: if len(mobile) != 11: mobile = input('输入的手机号不是11位,请重新输入:') else: break # 判断手机号是否已经存在 if mobile in [stu['mobile'] for stu in students_list]: mobile = input('该手机号已经存在,请重新输入:') else: break else: mobile = input('输入的手机号不是数字,请重新输入:') # 2.将学生信息保存到students_list中 # 2.1 创建一个字典,用来保存学生信息 stu_dict = {'id': stu_id, 'name': name, 'age': int(age), 'mobile': mobile} # 2.2 将学生信息添加到列表中 students_list.append(stu_dict) print('添加成功!') def del_student(): """删除学生""" # 1.获取用户输入的学生id stu_id = input('请输入要删除的学生ID:') # 2.判断学生id是否存在 if stu_id in [stu['id'] for stu in students_list]: # 3.如果存在,删除学生信息 for stu in students_list: if stu['id'] == stu_id: students_list.remove(stu) break print('删除成功!') else: print('该学生ID不存在!') def modify_student(): """修改学生""" # 1.获取用户输入的学生id stu_id = input('请输入要修改的学生ID:') # 2.判断学生id是否存在 if stu_id in [stu['id'] for stu in students_list]: # 3.如果存在,修改学生信息 for stu in students_list: if stu['id'] == stu_id: # 3.1 获取用户输入的学生信息 name = input('请输入学生姓名:') age = input('请输入学生年龄(数字):') mobile = input('请输入学生手机号:') # 3.2 修改学生信息 stu['name'] = name stu['age'] = age stu['mobile'] = mobile break print('修改成功!') else: print('该学生ID不存在!') def search_student(): """查询学生""" # 1.获取用户输入的学生id stu_id = input('请输入要查询的学生ID:') # 2.判断学生id是否存在 if stu_id in [stu['id'] for stu in students_list]: # 3.如果存在,查询学生信息 for stu in students_list: if stu['id'] == stu_id: print('学生ID\t学生姓名\t学生年龄\t学生手机号') print('%s\t%s\t%s\t%s' % (stu['id'], stu['name'], stu['age'], stu['mobile'])) break else: print('该学生ID不存在!') def show_all_student(): """显示所有学生""" # 1.判断学生列表是否为空 if len(students_list) == 0: print('没有学生信息,请先添加学生信息!') else: # 2.如果不为空,显示所有学生信息 print('学生ID\t学生姓名\t学生年龄\t学生手机号') for stu in students_list: print('%s\t%s\t%s\t%s' % (stu['id'], stu['name'], stu['age'], stu['mobile'])) print('共有%d名学生' % len(students_list)) def exit_system(): """退出系统""" print('退出系统成功!') exit() # def analysis_num(num): """分析用户输入的序号""" if num == '1': add_student() elif num == '2': del_student() elif num == '3': modify_student() elif num == '4': search_student() elif num == '5': show_all_student() elif num == '6': exit_system() else: print('输入错误,请重新输入') students_list = [{'id': '001', 'name': 'hanging', 'age': 18, 'mobile': '12345678901'}] while True: show_menu() num = get_num() analysis_num(num) input('按任意键继续...') ...
8、函数递归
- 函数内部调用函数本身
- 函数有明确的递归跳出条件
- 不超出最大调用深度
# 函数递归的三个必备条件 ''' 1/函数体内部,调用函数本身 2/递归够明确的跳出条件 3/不能超出最大调用深度 ''' # 需求: ''' func(1) = 1 func(2) = 1 + 2 = func(1) + 2 func(3) = 1 + 2 + 3 = func(2) + 3 func(4) = 1 + 2 + 3 + 4 = func(3) + 4 ..... func(n) = 1 + 2 + 3 .... + n = func(n-1) + n ''' # RecursionError: maximum recursion depth exceeded # 这种方式无法跳出递归,所以在使用的时候就会无限递归下去 # def func(n): # return func(n-1) + n ''' func(1) = 1 func(2) = func(1) + 2 func(3) = func(2) + 3 func(4) = func(3) + 4 ..... func(n) = func(n-1) + n ''' def func(n): if n == 1: return 1 return func(n-1) + n print(func(999)) # Python中默认的最大调用深度,是1000 也就是在Python中函数最多嵌套1000层 # 最大调用深度是为了保证系统性能的,否则无限递归下去,一会内存就满了 # 最大调用深度可以调整,可以调整到非常大的数字只要系统性能跟得上 # RecursionError: maximum recursion depth exceeded in comparison # 注意事项: # 在编程初期,尽量少使用递归,但是要理解递归的特性,别人写的递归函数也要能看懂
9、lambda函数
- 匿名函数,在函数定义时没有函数名
- 可以用变量保存,在变量之后添加括号即可调用
# lambda表达式,也叫匿名函数 # 格式: lambda 参数: 返回值 # 需求: 根据传入的参数返回最大值 使用lambda函数书写 # 三目运算 : 条件成立时返回的代码 if 条件 else 条件不成立时返回的代码 max_num = lambda a, b: a if a > b else b # 使用变量可以储存lambda函数 print(max_num(1, 2)) print(max_num(9, 2)) print(type(max_num)) # <class 'function'> def func(): print('abc') print(type(func)) # <class 'function'> # 通过对数据类型的查看,我们发现lambda和普通函数本质上是一样的,只不过在使用时构造更为简单 # 在使用lambda函数时可以不用变量接收 print((lambda a, b: a if a > b else b)(3, 4)) # 4 # 但是不适用变量接收,lambda函数只能使用一次,使用后集被释放,无法再次使用 # lambda缺点: 没有办法书写负责的函数,因为其没有函数体,只有返回值,所以返回值后边只能书写一个表达式,lambda可读性极差 # 使用lambda完成递归(了解,一般不建议写复杂的代码) func1 = lambda n: func1(n - 1) + n if n != 1 else 1 # RecursionError: maximum recursion depth exceeded # 超出最大调用深度,没有明确的递归跳出条件 print(func1(100)) # lambda应用场景 # 可以用于一次性函数使用 # 可以作为函数的参数进行传递 # list sort(key= ) # lsit sort排序方法中的key所需要的参数就是一个函数,我们可以传入lambda表示 list1 = [{'a': 1}, {'b': 12}, {'c': 10}] # 排序规则:根据字典的第一个键所对应的值进行排序 list1.sort(key=lambda x:list(x.values())[0]) # 格式: 列表.sort(key = lambda 每次传入的元素: 排序所依据的规则) print(list1)