Python 函数
一.函数的初识
函数:以功能(完成一件事)为导向,登录,注册,len一个函数就是一个功能。随调随用
函数的优势
1.减少了代码的重复性。
2.增强了代码的可读性
二.函数的结构和调用
2.1 函数的结构
def 函数名():
函数体
def关键词开头,空格之后接函数名称和圆括号(),最后还有一个”:”
def 是固定的,不能变,他就是定义函数的关键字
空格 为了将def关键字和函数名分开,必须空,当然你可以空2格、3格,但正常人还是空一个
函数名:函数名只能包含字符串、下划线和数字且不能以数字开头。虽然函数名可以随便起,但我们给函数起名字还是要尽量简短,并且要具有可描述性
括号: 是必须加的,先别问为啥要有括号,总之加上括号就对了(下面就会讲到)!
下面的函数体一定全部都要缩进,这代表是这个函数的代码。
2.2 函数的调用
使用函数名加小括号就可以调用函数了 写法:函数名(),这个时候函数的函数体会被执行
而且是这个指令你写几次,函数里面的代码就运行几次
三.函数的返回值
一个函数就是封装一个功能,这个功能一般都会有一个最终结果的,比如你写一个登录函数,最终登录成功与否是不是需要返回你一个结果?还有咱们是不是都用过len这个函数,他是获取一个对象的元素的总个数,最终肯定会返回一个元素个数这样的结果:
那么这个返回值如何设置呢?这就得用到python中的一个关键字:return
1.函数中遇到return,此函数结束.不在继续执行
def date(): print("拿出手机") print("打开陌陌") print('左滑一下') print('右滑一下') print("找个漂亮的妹子") return print("问她,约不约啊!") print("ok 走起") date() 结果: D:\pythonProject\python3\venv\Scripts\python.exe D:\pythonProject\python3\day10\s2.py 拿出手机 打开陌陌 左滑一下 右滑一下 找个漂亮的妹子
2.return 会给函数的执行者返回值。
如果return后面什么都不写,或者函数中没有return,则返回结果是None
如果return后面写一个值,返回给调用者这个值
如果return后面写多个结果,返回给调用者一个tuple(元组),调用者可以直接使用元组解构获取多个变量
def date(): print("拿出手机") print("打开陌陌") print("找个漂亮的妹子") print("问她,约不约啊!") print("ok 走起") print(date()) # None def date(): print("拿出手机") print("打开陌陌") print("找个漂亮的妹子") print("问她,约不约啊!") print("ok 走起") return '约起' print(date(),type(date())) # 约起 <class 'str'> def date(): print("拿出手机") print("打开陌陌") print("找个漂亮的妹子") print("问她,约不约啊!") print("ok 走起") return '约起','OK 走起' print(date(),type(date())) # 约起 <class 'str'> a1,a2 =date() print(a1,a2) #约起 OK 走起
四.函数的参数
函数的参数可以从两个角度划分:
1.行参
写在函数声明的位置的变量叫行参,形式上的一个完整,表示这个函数需要XXX
2.实参
在函数调用的时候给函数传递的值叫实参,实际执行的时候给函数传递的信息,表示给函数XXX
def date(sex): # sex就是行参 print('设置筛选性别 %s' %(sex)) date('女') # 女就是实参
4.1 实参角度
1.位置参数
位置参数就是从左到右,实参与形参一一对应
def msg(sex,age,hobby): print('人员信息 性别:%s 年龄:%s 兴趣爱好:%s' %(sex,age,hobby)) msg('女',25,'弹钢琴') # 实参和形参位置一一对应 msg('人妖',20,'钢管舞')
联系题:
# 编写函数,给函数传递两个参数a,b a,b相加 返回a参数和b参数相加的和 def f_sum(a,b): c = a + b return c print(f_sum(100,23))
#比较大小的这个写法有点麻烦,我们在这里学一个三元运算符(单个if else语句可以改为一个三元运算符) def f_size(a,b): return b if a < b else a print(f_size(100,200)) #200
2.关键字参数
位置参数如果在参数比较少的时候很方便,如果参数比较多时,需要记住每个参数的位置就比较困难了,这个时候我们
就需要关键字参数了,我们只需要记住参数的名字就可以了
def msg(sex,age,hobby): print('人员信息 性别:%s 年龄:%s 兴趣爱好:%s' %(sex,age,hobby)) msg(age=25,hobby='弹钢琴',sex='女') # 关键字参数不需要关系参数的位置
3.混合参数
可以吧位置参数和关键字参数混合使用,但是需要注意关键字参数一定在位置参数后面
def msg(sex,age,hobby): print('人员信息 性别:%s 年龄:%s 兴趣爱好:%s' %(sex,age,hobby)) msg('女',hobby='音乐',age=16) #人员信息 性别:女 年龄:16 兴趣爱好:音乐
4.2 形参角度
1.位置参数
位置参数其实与实参的位置参数是一样的就是按照位置从左到右
2.默认值参数
在函数声明的时候,可以给函数参数默认值。默认值参数一般是这个参数使用率较高,在位置参数后添加
# 例如大部分公司的操作系统都是linux def msg(name,scale,system='Linux'): print('公司信息 名称:%s 规模:%s 操作系统:%s' %(name,scale,system)) msg('阿里巴巴',10000) # 公司信息 名称:阿里巴巴 规模:10000 操作系统:Linux msg('腾讯',100000,'Windows') # 公司信息 名称:腾讯 规模:100000 操作系统:Windows
练习题:
#写函数,检查列表的长度,如果大于2,那么仅保留前2个长度内容,并将新内容返回给调用者 def trunc_lis(lis): # if len(lis) > 2: # return lis[0:2] # else: # return lis return lis[0:2] if len(lis) > 2 else lis # 或者 return lis[:2] l1 = [1,2,3,4,5,6] l2 = ['a',2] ret1 = trunc_lis(l1) print(ret1,type(ret1)) ret2 = trunc_lis((l2)) print(ret2,type(ret2))
1.写函数,检查获取传入列表或元组对象的所有奇数位索引对应的元素,并将其作为新的列表返回给调用者 new_li = [] def func(l1): for i in range(len(l1)): if i % 2 != 0: new_li.append(l1[i]) else: pass return new_li print(func(['aa',11,'bb',22,'cc','dd'])) # [11, 22, 'dd'] 2.写函数,判断用户传入的对象(字符串、列表、元组)长度是否大于5 def func(a): return '是' if len(a) > 5 else '否' print(func('as'),func('adfadgds'),func([1,2,3,4,5,6])) # 否 是 是 3.写函数,检查列表长度,如果大于2,那么仅保留前两个长度的内容,并将新内容返回给调用者 def func(l1): return l1[:2] print(func([1,2,3,4]),func(['a'])) # [1, 2] ['a'] 4.写函数,检查传入函数的字符串中,数字、字母、以及其他的个数,并返回结果 def func(str1): num_sum = 0 abc_sum = 0 oth_sum = 0 for i in str1: if i.isdigit(): num_sum += 1 elif i.isalpha(): abc_sum += 1 else: oth_sum += 1 return '数字:%s 字母:%s 其他:%s' %(num_sum,abc_sum,oth_sum) print(func('agfadg123,,,....')) # 数字:3 字母:6 其他:7 5.写函数,接收两个数字参数,返回比较大的那个数字 def func(a,b): return a if a > b else b print(func(11,23)) # 23 6.写函数,检查插入字典的每一个value的长度,如果大于2,那么仅保留前两个长度的内容,并将新内容返回给调用者d dic = {'k1': 'v1v1','k2':[11,22,33,44]} ps:字典中的value只能是字符串或者列表 def func(d1): new_dic = {} for key,value in d1.items(): new_dic[key] = value[:2] return new_dic dic = {'k1': 'v1v1','k2':[11,22,33,44]} print(func(dic)) # {'k1': 'v1', 'k2': [11, 22]} 7.写函数,此函数只接收一个参数必须是列表数据类型,此函数完成的功能是返回给调用者一个字典 此字典的键值对为此列表的索引及对应的元素。例如传入的列表为[11,22,33] 返回的字典韦{0:11,1:22,3:33} def func(l1): dic = {} for i in range(len(l1)): dic[i] = l1[i] return dic print(func([11,22,33])) # {0: 11, 1: 22, 2: 33} 8.写函数,函数接收4个参数分别是姓名,性别,年龄,学历 用户通过输入这4个内容,然后将这4个内容传入到函数中,此函数接收到这4个内容,将内容追加到一个student_msg.txt文件中 def func(name,sex,age,education): with open('student_msg.txt',encoding='utf-8',mode='w') as f: f.write('{}|{}|{}|{}|'.format(name,sex,age,education)) func('小白','男',30,'大专') 9.对8题升级:支持用户持续输入,Q或者q退出,性别默认为男,如遇女同学,则把性别为女 def func(name,age,education,sex): with open('student_msg.txt',encoding='utf-8',mode='a') as f: f.write('{}|{}|{}|{}|\n'.format(name,sex,age,education)) while 1: name = input('请输入姓名(Q或者q退出):') if name.upper() == 'Q': break sex = input('请输入性别:') if sex: pass else: sex = '男' age = input('请输入年龄:') education = input('请输入学历:') func(name,age,education,sex) 10.写函数,用户传入修改的文件名,与要修改的内容,执行函数,完成整个文件的批量修改操作 import os def func(file_name,source_msg,target_msg): with open(file_name,encoding='utf-8',mode='r') as f1,open(file_name + '.bak',encoding='utf-8',mode='w') as f2: for line in f1: line = line.replace(source_msg,target_msg) f2.write(line) os.remove(file_name) os.rename(file_name + '.bak',file_name) func('student_msg.txt','女','男')
3.形参的第三种:动态参数
动态参数分为两种:动态接收位置参数*args,动态接收关键字参数**kwargs。
a.动态接收位置参数:*args
def func(*args): print('打印数字:' ,args) func(1,2,3,4,5,6) # 结果 打印数字: (1, 2, 3, 4, 5, 6)
args就是一个普通的形参。但是如果在args前面加上*,就有特殊的意义,*args如果这样设置形参,这个形参会将所有的
位置参数接收,放置在一个元组中,并将这个元组赋值给args这个形参。注意这里是*的效果而不是args,a也是可以达到
刚才的效果,但是值PEP8规范中就规定使用args,约定俗成的
练习:传入函数中数量不定的int型数据,函数计算素有数的和并返回
def func(*args): count = 0 for i in args: count += i return count print(func(1,2,3,4,5,6,7,8)) # 36
b. 动态关键字参数:**kwargs
实参角度有位置参数和关键字参数两种,**kwargs用来接收所有的关键字参数。kwargs也是约定俗成的,**kwargs,是接受所有的关键字参数然后将其转换成一个字典赋值给kwargs这个形参
def func(**kwargs): print(kwargs) func(name='小白',age=18,sex='男') # {'name': '小白', 'age': 18, 'sex': '男'}
4.3 *的魔性用法
a.聚合
在函数定义时*、** 起到的作用是聚合(*将结果聚合为元组,**将结果聚合为字典)
b.打散
在函数执行时*、** 起到打散的作用
# *打散案例 s1 = 'test' l1 = [1, 2, 3, 4] tu1 = ('污sir', '小白', '女神',) def func(*args): print(args) # ('t', 'e', 's', 't', 1, 2, 3, 4, '污sir', '小白', '女神') func(*s1,*l1,*tu1) # ** 打散案例 dic1 = {'name': '太白', 'age': 18} dic2 = {'hobby': '喝茶', 'sex': '男'} def func(**kwargs): print(kwargs) # {'name': '太白', 'age': 18, 'hobby': '喝茶', 'sex': '男'} func(**dic1,**dic2)
4.4 形参的顺序
位置参数,*args,默认参数,**kwargs
def func(name,*args,sex='男',**kwargs): print(name) print(args) print(sex) print(kwargs) func('小白','test','a',[1,2,3],age=19,hobby='音乐') 结果: D:\pythonProject\python3\venv\Scripts\python.exe D:\pythonProject\python3\day10\s1.py 小白 ('test', 'a', [1, 2, 3]) 男 {'age': 19, 'hobby': '音乐'}
4.5 形参的第四种参数:仅限关键字参数
仅限关键字参数是python3x更新的新特性,他的位置在*args后面,在**kwargs前面
,也就是默认参数的位置,它与默认参数的前后顺序无所谓,它只接收关键字传的
参数
def func(name,*args,age,sex='男',**kwargs): print(name) print(args) print(sex) print(age) print(kwargs) func('小白','test','a',[1,2,3],age=19,hobby='音乐') 结果: D:\pythonProject\python3\venv\Scripts\python.exe D:\pythonProject\python3\day10\s1.py 小白 ('test', 'a', [1, 2, 3]) 男 19 {'hobby': '音乐'}
这个仅限关键字参数从名字定义就可以看出他只能通过关键字参数传参,其实可以把它当成不设置默认值的默认参数而且必须要传参数,不传就报错。
所以形参角度的所有形参的最终顺序为:位置参数,*args,默认参数,仅限关键字参数,**kwargs。(注:关键字参数和默认参数顺序可以调换)
五、名称空间、作用域
5.1 名称空间
在python解释器开始执行之后, 就会在内存中开辟一个空间, 每当遇到一个变量的时候, 就把变量名和值之间的关系记录下来, 但是当遇到函数定义的时候, 解释器只是把函数名读入内存, 表示这个函数存在了, 至于函数内部的变量和逻辑, 解释器是不关心的. 也就是说一开始的时候函数只是加载进来, 仅此而已, 只有当函数被调用和访问的时候, 解释器才会根据函数内部声明的变量来进行开辟变量的内部空间. 随着函数执行完毕, 这些函数内部变量占用的空间也会随着函数执行完毕而被清空.
我们首先回忆一下Python代码运行的时候遇到函数是怎么做的,从Python解释器开始执行之后,就在内存中开辟里一个空间,每当遇到一个变量的时候,就把变量名和值之间对应的关系记录下来,但是当遇到函数定义的时候,解释器只是象征性的将函数名读如内存,表示知道这个函数存在了,至于函数内部的变量和逻辑,解释器根本不关心。
等执行到函数调用的时候,Python解释器会再开辟一块内存来储存这个函数里面的内容,这个时候,才关注函数里面有哪些变量,而函数中的变量回储存在新开辟出来的内存中,函数中的变量只能在函数内部使用,并且会随着函数执行完毕,这块内存中的所有内容也会被清空。
我们给这个‘存放名字与值的关系’的空间起了一个名字——-命名空间。
代码在运行伊始,创建的存储“变量名与值的关系”的空间叫做全局命名空间;
在函数的运行中开辟的临时的空间叫做局部命名空间也叫做临时名称空间。
现在我们知道了,py文件中,存放变量与值的关系的一个空间叫做全局名称空间,而当执行一个函数时,内存中会临时开辟一个空间,临时存放函数中的变量与值的关系,这个叫做临时名称空间,或者局部名称空间。
其实python还有一个空间叫做内置名称空间:内置名称空间存放的就是一些内置函数等拿来即用的特殊的变量:input,print,list等等,所以,我们通过画图捋一下:
那么这就是python中经常提到的三个空间。
总结:
\1. 全局命名空间–> 我们直接在py文件中, 函数外声明的变量都属于全局命名空间
\2. 局部命名空间–> 在函数中声明的变量会放在局部命名空间
\3. 内置命名空间–> 存放python解释器为我们提供的名字, list, tuple, str, int这些都是内置命名空间
5.2 加载顺序
内置命名空间(程序运行伊始加载)->全局命名空间(程序运行中:从上到下加载)->局部命名空间(程序运行中:调用时才加载。
5.3 取值顺序(就近原则)
局部命名空间->全局命名空间->内置命名空间(程序运行伊始加载)
间的取值顺序与加载顺序是相反的,取值顺序满足的就近原则,从小范围到大范围一层一层的逐步引用。
5.4 作用域
作用域就是作用范围, 按照生效范围来看分为全局作用域和局部作用域
全局作用域: 包含内置命名空间和全局命名空间. 在整个文件的任何位置都可以使用(遵循 从上到下逐⾏执行).
局部作用域: 在函数内部可以使用.
作⽤域命名空间:
1. 全局作用域: 全局命名空间 + 内置命名空间
2. 局部作⽤域: 局部命名空间
5.5 内置函数lobals(),locals()
globals(): 以字典的形式返回全局作用域所有的变量对应关系。
locals(): 以字典的形式返回当前作用域的变量的对应关系。
a = 2 print(globals()) def func(): c =3 print(locals()) func() 结果: D:\pythonProject\python3\venv\Scripts\python.exe D:\pythonProject\python3\day10\s1.py {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000001E86C077C40>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'D:\\pythonProject\\python3\\day10\\s1.py', '__cached__': None, 'a': 2} {'c': 3}
5.6 高阶函数(函数的嵌套)
关键点:只要遇见了函数名+()就是函数的调用. 如果没有就不是函数的调用
# 例1: def func1(): print('in func1') print(3) def func2(): print('in func2') print(4) func1() print(1) func2() print(2) 结果: in func1 3 1 in func2 4 2 # 例2: def func1(): print('in func1') print(3) def func2(): print('in func2') func1() print(4) print(1) func2() print(2) 结果: 1 in func2 in func1 3 4 2 # 例3: def fun2(): print(2) def fun3(): print(6) print(4) fun3() print(8) print(3) fun2() print(5) 结果: 3 2 4 6 8 5
5.7 关键字:global、nonlocal
global
局部作用域对全局作用域的变量(此变量只能是不可变的数据类型)只能进行引用,而不能进行改变,只要改变就会报错
global第一个功能:在局部作用域中可以更改全局作用域的变量。
# 直接改变报错 a = 1 def func(): a += 1 return a func() print(a) 结果: UnboundLocalError: local variable 'a' referenced before assignment # 使用globals a = 1 def func(): global a a += 1 return a func() print(a) 结果:2
global的第二个功能:利用global在局部作用域也可以声明一个全局变量
def func(): global a a =3 func() print(a) # 3
所以global 关键字有两个作用:
1.声明一个全局变量
2.在局部作用域想对全局作用域的全局变量进行修改是,需要用到global关键字
nonlocal
nonlocal是python3x新加的功能,与global用法差不多,就是在局部作用域如果想对父级作用域的变量进行改变时,需要用到nonlocal,当然这个用的不是很多,了解即可。
def add_b(): b = 42 def do_global(): b = 10 print(b) def dd_nonlocal(): nonlocal b b = b + 20 print(b) dd_nonlocal() print(b) do_global() print(b) add_b() 结果: 10 30 30 42
nilocal总结:
1.不能修改全局变量
2.在局部作用域中,对父级作用域(或者更外层的作用域非全局作用域)的变量进行引进和修改,并且引用的哪层,从那层及以下此变量全部发生改变
练习题
# # 写函数,函数可以支持接收任意数字(位置传参)并将所有数字相加并返回 # def func(*args): # count = 0 # for i in args: # count += i # return count # # print(func(1,2,3,4,5,6)) # # 看代码写结果 # def func(): # return 1,2,3 # # va1 = func() # print(type(va1)) # tuple # # 看代码写结果 # def func(*args,**kwargs): # print(args) # print(kwargs) # # a.请将执行函数,并实现让args值为(1,2,3,4) # func(1,2,3,4) # func(*[1,2,3,4]) # # b.请将执行函数,并实现让args的值为([1,2,3,4],[11,22,33]) # func([1,2,3,4],[11,22,33,44]) # # c.请将执行函数,并实现args的值为([11,22],33)且kwargs的值{'k1':'v1','k2':'v2'} # func([11,22],33,k1='v1',k2='v2') # # d.如执行func(*{'吴佩琪','金星','女神'}),请问args和kwargs的值分别为 # func(*{'吴佩琪','金星','女神'}) # ('金星', '吴佩琪', '女神') {} # # e.如执行func({'吴佩琪','金星','女神'},[11,22,33])请问args和kwargs的值分别为 # func({'吴佩琪','金星','女神'},[11,22,33]) # ({'吴佩琪','金星','女神'},[11,22,33]) {} # # f.如执行func('吴佩琪','金星','女神',[11,22,33],**{'k1':'v1'}),请问args和kwargs的值分别为 # func('吴佩琪','金星','女神',[11,22,33],**{'k1':'v1'}) #('吴佩琪', '金星', '女神', [11, 22, 33]) {'k1': 'v1'} # # 看代码写结果 # def func(name,age=19,email='123@qq.com'): # print(name,age,email) # # #a.执行func('alex'),判断是否可执行,如可以请问name、age、email的值分别是? # func('alex') #可以执行 结果:alex 19 123@qq.com # #b.执行func('alex',20),判断是否可执行,如可以请问name、age、email的值分别是? # func('alex',20) #可以执行 结果:alex 20 123@qq.com # #c.执行func('alex',20,30),判断是否可执行,如可以请问name、age、email的值分别是? # func('alex',20,30) #可以执行 结果:alex 20 30 # #d.执行func('alex',email='x@qq.com'),判断是否可执行,如可以请问name、age、email的值分别是? # func('alex',email='x@qq.com') #可以执行 结果:alex 19 x@qq.com # #e.执行func('alex',email='x@qq.com',age=99),判断是否可执行,如可以请问name、age、email的值分别是? # func('alex',email='x@qq.com',age=99) #可以执行 结果:alex 99 x@qq.com # #f.执行func(name='alex',99),判断是否可执行,如可以请问name、age、email的值分别是? # func(name='alex',99) #不可以执行 位置参数在关键字参数之后 # #g.执行func(name='alex',99,'111@qq.com'),判断是否可执行,如可以请问name、age、email的值分别是? # func(name='alex',99,'111@qq.com') #不可以执行 位置参数在关键字参数之后 # # 看代码写结果 # def func(users,name): # users.append(name) # return users # result = func(['吴佩琪','李杰'],'alex') # print(result) #['吴佩琪', '李杰', 'alex'] # # 看代码写结果 # def func(v1): # return v1 * 2 # # def bar(arg): # return "%s 是什么玩意?" %(arg,) # # va1 = func('你') # data = bar(va1) # print(data) # 你你 是什么玩意? # # # 看代码写结果 # def func(): # v1 = '仓女神' # def inner(): # print(v1) # v1 = '肖大虾' # inner() # # func() # v1 = '老男人' # func() # # # 结果:肖大虾 肖大虾 # # 看代码写结果 # a = 10 # b = 20 # def test5(a, b): # print(a, b) # c = test5(b, a) # print(c) # 20 10 None # # 看代码写结果 # a = 10 # b = 20 # def test5(a,b): # a = 3 # b = 5 # print(a,b) # c = test5(b,a) # print(c) # 3 5 None # # # 看代码写结果 # def wrapper(): # def inner(): # print(666) # # wrapper() # 无返回结果
注意:
# 默认参数为可变变量的坑,第二调用函数会继续使用第一次的可变变量继续修改 def func(a,b=[]): b.append(a) return b ret1 = func(1) print(ret1,id(ret1)) ret2 = func(2) print(ret2,id(ret2)) 结果: [1] 1942625506560 [1, 2] 1942625506560