函数存在的意义?
之前我们都已经熟悉的使用过python自带的len()方法用来获取字符串的长度,但设想如果某一天这个方法突然不能用了,我们应该怎么办?以计算字符串hello world的长度为例,想实现这一需求并不麻烦:
s = 'hello world' def my_len(): i = 0 for k in s: i += 1 return i length = my_len() print(length) # 11
由此改功能得以实现,但是倘若我们想计算字符串“I love you”的长度,不得不修改S的值再进行类似的操作,虽然也能够达到目的,但总体感觉上来说总显得不够完美。为什么呢?
首先,之前只要我们执行len方法就可以直接拿到一个字符串的长度了,现在为了实现相同的功能我们把相同的代码写了好多遍 —— 代码冗余
其次,之前我们只写两句话读起来也很简单,一看就知道这两句代码是在计算长度,但是刚刚的代码却不那么容易读懂 —— 可读性差
于是,我们也想定义一个类似于len()的方法用来实现我们需要经常使用的操作,等到我们需要使用的时候直接调用函数名就能执行特定的代码一般。
函数定义与调用
先附上可以实现上述需求的一个示例函数的代码:
def mylen(): s1 = "hello world" length = 0 for i in s1: length = length+1 print(length)
函数定义:
def 关键词开头,空格之后接函数名称和圆括号(),最后还有一个":"。
def 是固定的,不能变,必须是连续的def三个字母,不能分开。。。它们要相亲相爱的在一起。
空格 为了将def关键字和函数名分开,必须空(四声),当然你可以空2格、3格或者你想空多少都行,但正常人还是空1格。
函数名:函数名只能包含字符串、下划线和数字且不能以数字开头。虽然函数名可以随便起,但我们给函数起名字还是要尽量简短,并能表达函数功能
括号:是必须加的,先别问为啥要有括号,总之加上括号就对了!
函数调用:
就是 函数名() ps:要记得加上括号
注意:另外函数也有专属于他自己的注释风格
# 函数的注释 def func(): ''' 这个函数实现了什么功能 参数1: 参数2: :return:是字符串或者列表的长度 ''' pass
函数的返回值
return关键字:该词翻译过来是“返回”的意思,故我们管其后的值为“返回值”。要想弄清楚返回值,我们得先知道返回值的几种情况:没有放回值、返回一个值、返回多个值
没有返回值:
不写return和只写return后面不写其他内容的情况下,函数都会返回一个None。你可能会因此产生疑问:既然没有要返回的值,为何嗨呀写一个return呢?这里要强调一下return的其他用法:即一旦你遇上return,就会结束整个函数,return后面的语句将不会再执行。
返回一个值:
这种情况只需在return后面加上要返回的值即可。
注意:return可以返回任意数据类型的值,但是return和返回值之间要留有空格。
返回多个值:
return后面也可以返回任意多个、任意数据类型的值
def ret_demo1(): '''返回多个值''' return 1,2,3,4 def ret_demo2(): '''返回多个任意类型的值''' return 1,['a','b'],3,4 ret1 = ret_demo1() print(ret1) ret2 = ret_demo2() print(ret2)
返回的多个值会被组织成元组被返回,也可以用多个值来接收
def ret_demo2(): return 1,['a','b'],3,4 #返回多个值,用一个变量接收 ret2 = ret_demo2() print(ret2) #返回多个值,用多个变量接收 a,b,c,d = ret_demo2() print(a,b,c,d) #用多个值接收返回值:返回几个值,就用几个变量接收 a,b,c,d = ret_demo2() print(a,b,c,d)
PS:在python中,用逗号分割的多个值就被认为是一个元祖。
函数的参数
本篇开始就说到如果有需求想要计算另外字符串的长度,但又不想重复编写复杂的代码的时候,我们应该怎么做?其实只要定义一个带有参数的模板,将要计算的字符串赋给这个参数传入函数进行计算即可。这个过程就被称为传递参数,简称传参。
形参和实参
传参的过程种又有实参和形参的区别:
实参,全称为实际参数,简单的说就是我们想要进行处理的值,也是后续过程实际要交给函数的内容。
形参,全称为形式参数,只是一个变量名,在定义函数的时候它只是一个形式,表示这里有一个参数。
传递多个参数
参数可以传递多个,多个参数之间用逗号分割。
正因为多个参数的存在,才有这后续的一系列操作。。。
位置参数
站在实参角度
1、按照位置传值
def mymax(x,y): #此时x=10,y=20 the_max = x if x > y else y return the_max ma = mymax(10,20) print(ma) #20
2、按照关键字传值
def mymax(x,y): #此时x = 20,y = 10 print(x,y) the_max = x if x > y else y return the_max ma = mymax(y = 10,x = 20) print(ma) #20
3、位置、关键字形式混合传值
def mymax(x,y): #此时x = 10,y = 20 print(x,y) the_max = x if x > y else y return the_max ma = mymax(10,y = 20) print(ma) #20
注意:在位置和关键字两种形式混合使用的情况下,必须遵循“一个形参赋值一次,位置参数必须在关键字参数前面”的原则。
默认参数
由来:传参过程中㝉出现某些值重复出现的情况,此时将变化比较小的值设置成默认参数。
def stu_info(name,sex = "male"): """打印学生信息函数,由于班中大部分学生都是男生, 所以设置默认参数sex的默认值为'male' """ print(name,sex) stu_info('凝宝') #凝宝 male stu_info('柳柳','female') #柳柳 female
参数陷阱:默认参数是一个可变数据类型
#如果默认参数的值是一个可变数据类型, # 那么每一次调用函数的时候,如果不传值就共用这个数据类型的资源 def qqxing(l=[]): l.append(1) print(l) qqxing()#[1] qqxing([])#[1] qqxing()#[1, 1] qqxing()#[1, 1, 1]
动态参数
可以接受任意多个参数
两种:
*args:接收的是按照位置传参的值,组织成一个元组
**kwargs:接收的是按照关键字传参的值,组织成一个字典
args必须在kwargs之前
def sum(*args): n = 0 for i in args: n += i return n print(sum(1, 2)) # 3 print(sum(1, 2, 3)) # 6 print(sum(1, 2, 3, 4)) # 10
def func(**kwargs): print(kwargs) func(a=1, b=2, c=3) # {'a': 1, 'b': 2, 'c': 3} func(a=1, b=2) # {'a': 1, 'b': 2} func(a=1) # {'a': 1}
def func1(*args, **kwargs): print(args, kwargs) func1(1, 2, 3, 4, 5, a='adsd', b='cda') # (1, 2, 3, 4, 5) {'a': 'adsd', 'b': 'cda'}
总顺序:位置参数,*args,默认参数,**kwargs
命名空间和作用域
命名空间
命名空间的本质:存放名字与值的绑定关系
命名空间一共分为三种:
内置命名空间--python解释器
就是python解释器一启动就可以使用的名字存储在内置明明空间中
内置的名字在启动解释器的时候加载进内存
全局命名空间--我们写的代码但不是函数中的代码
程序在执行过程中依次加载进内存
放置了我们设置的所有变量名和函数名
局部命名空间--函数
就是函数内部定义的名字
当调用函数的时候才会产生这个名称空间,随着函数执行的结束,这个明明空间就又消失了
三种命名空间之间的加载与取值顺序:
加载顺序:内置命名空间(程序运行前加载)->全局命名空间(程序运行中:从上到下加载)->局部命名空间(程序运行中:调用时才加载)
取值:在局部调用:局部命名空间->全局命名空间->内置命名空间
在全局调用:全局命名空间->内置命名空间
#在局部:可以使用全局、内置命名空间中的名字
#在全局:可以使用内置命名空间的名字,但是不能使用局部的
#在内置:不能使用局部和全局的名字的
#范围:内置>全局>局部(小范围可以使用大范围)
#在正常情况下,直接使用内置的名字
#当我们在全局中定义了与内置同名的名字时会使用全局的名字
#多个函数应该拥有多个独立的局部名字空间,不互相共享
作用域
全局作用域-->作用在全局:内置和全局名字空间中的名字都属于全局作用域
局部作用域-->作用在局部:函数(局部命名空间中的名字都属于局部作用域) locals()查看
对于不可变数据类型:在局部可以查看全局作用域中的变量,但是不能直接修改。如果想要修改,需要在程序的一开始添加global声明
如果在一个局部(函数)内声明了一个global变量,那么这个变量在局部的所有操作将对全局的变量有效
a=1 def fun(): global a a+=1 fun() print(a) #2
a=1 b=2 def fun(): x='aaa' y='bbb' print(locals())#{'x': 'aaa', 'y': 'bbb'} fun() print(globals())#{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000016EB4011CC0>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'E:/Python/day07/函数进阶.py', '__cached__': None, 'func1': <function func1 at 0x0000016EB531A9D8>, 'a': 1, 'b': 2, 'fun': <function fun at 0x0000016EB531AA60>} print(locals())#本地的 #globals永远打印全局的名字 #locals 输出什么 根据locals所在的位置
函数的嵌套和作用域链
函数的嵌套调用
def max(a,b): return a if a>b else b def my_max(x,y,z): c=max(x,y) return max(c,z) print(my_max(1,2,3)) #3
函数的嵌套定义
def f1(): print("in f1") def f2(): print("in f2") f2() f1()
def f1(): def f2(): def f3(): print("in f3") print("in f2") f3() print("in f1") f2() f1()
函数的作用域链
def f1(): a = 1 def f2(): print(a) f2() f1()
def f1(): a = 1 def f2(): def f3(): print(a) f3() f2() f1()
def f1(): a = 1 def f2(): a = 2 f2() print('a in f1 : ',a) f1()
nonlocal:只能用于局部变量,找上一层中离当前函数最近一层的局部变量 -->对全局无效,对局部也只是对最近的一层有影响
声明了nonlocal的内部函数的变量修改会影响到离当前函数最近一层的局部变量
a=1 def outer(): a=1 def inner(): a=2 print(a) print("inner") def inner2(): nonlocal a #声明了一个上面第一层局部变量 a+=1 print('inner2') inner2() print("##a##",a) inner() print("**a**:",a) outer() print("**a**:",a)
函数名的本质
函数名本质上就是函数的内存地址
1、可以被引用
def func(): print('in func') f = func print(f) #<function func at 0x000001DBF88930D0>
2、可以被当做容器类型的元素
def f1(): print('f1') def f2(): print('f2') def f3(): print('f3') l = [f1,f2,f3] d = {'f1':f1,'f2':f2,'f3':f3} #调用 l[0]() d['f2']()
3、可以当做函数的参数和返回值
def func(): print(123) def wahaha(f): f() return f #函数名可以作为函数的返回值 qqxing=wahaha(func) #函数名可以作为函数的参数 123 qqxing()
闭包
闭包函数:
内部函数包含对外部作用域而非全剧作用域名字的引用,该内部函数称为闭包函数
#函数内部定义的函数称为内部函数
def outer(): a=1 def inner(): print(a) print(inner.__closure__) #(<cell at 0x0000029E98BC7678: int object at 0x00007FFADB639340>,) outer()
如上述代码中,inner函数即为闭包函数,可用__closure__方法进行判断
闭包函数常用方法
def outer(): a=1 def inner(): print(a) return inner inn=outer() inn()
闭包的嵌套:
def wrapper(): money = 1000 def func(): name = '凝宝' def inner(): print(name,money) return inner return func f = wrapper() i = f() i()