函数
- 由诺干语句组成的语句块、函数名、参数列表构成,它是组织代码的最小单元
- 能完成一定的功能
- 是一个可调用对象,可以使用callable(函数名)判断该名称是否是一个可调用对象
- 数学定义:y=f(x), y是x的函数,x是自变量。y=f(x0,x1,…xn)
函数的作用
- 结构化编程对代码的最基本封装,一般按照功能组织一段代码
- 封装的目的为了复用,减少冗余代码
- 代码更加简洁美观、可读易懂
函数的分类
- 内建函数,如:max()、reversed()等
- 库函数,如math.ceil()
函数的定义
- def语句定义函数
- def 函数名(参数列表):
函数体(代码块)
[return 返回值] - 函数名就是标识符。命名要求和标识符命名要求一样。
- 语句块必须缩进,约定4个空格
- Python的函数如果没有return语句,那么默认会返回None值
- 定义中的参数列表成为形式参数,只是一种符号表达(标识符),简称形参
- 例如:
##定义无参函数 def Hellogdy(): print("gdy") ##函数的调用 Hellogdy() ##加法函数 def add(x,y): return x+y ##调用函数 print(add(7,8))
- def 函数名(参数列表):
函数的调用
- 函数定义,只是在内存中声明了一个函数。需要调用才能执行该函数
- 调用方法:函数名加上小括号,如果有必要参数,需要在括号内写上参数
- 调用时写的参数是实际参数,是实实在在传入的值,简称实参
函数的参数
- 参数是调用函数时传入的。参数要和函数定义的形参想匹配(可变参数例外)
- 位置参数:按照参数定义顺序传入实参
- 例如: def f(x,y,z)调用使用f(1,3,5)
- 关键字参数:使用形参的名字来传入实参的方式。如果使用了关键字参数,那么传参顺序就可以和定义顺序不同
- 例如: def f(x,y,z)调用使用f(x=1,z=2,y=3)
- 混合调用
- 如果使用位置参数和关键字参数混合调用。那么位置参数必须要在关键字参数之前传入,参数不能重复
*例子:
- 如果使用位置参数和关键字参数混合调用。那么位置参数必须要在关键字参数之前传入,参数不能重复
def add(x,y,z):
return x+y+z
#使用位置参数调用
add(1,2,3) #此时形参x=1,y=2,z=3的方式按照顺序获取实参
#使用关键字参数调用
add(z=1,y=3,x=5) #直接指定形参的值调用函数
#形参和实参混合调用
add(4,z=5,y=3) ##正确, #错误写法add(4,x=3,z=8)
函数参数的默认值(缺省值)
- 在定义函数的形参时,给形参附加一个值。
- 例如:
#4为函数形参x的默认值,5为函数形参y的默认值
def add(x=4,y=5):
return x+y
- 注意:如果定义函数时,有缺省值的参数必须放在没有缺省值参数的后面。
例如:
def add(x,y=5):
return x+y
- 作用
- 参数的默认值可以在未传入足够的实参时,对没有给定的参数赋值即为默认值
- 参数非常多的时候,并不需要用户每次都输入所有的参数,简化函数调用
函数的可变参数
-
可变位置参数
- 表示方法:【*变量名】
- 一个形参可以匹配任意个参数。会收集多个实参组成一个tuple
- 注意:
- 可变位置参数必须要在位置参数后面
- 可变位置参数不能使用关键字传参
例如:
def addsum(*args): sum = 0 for x in args: sum += i return sum #调用 addsum(1,23,4,5,65,6) #返回所有参数的和 def addsum2(a,b=4,*args): print(a,b,args)
-
可变关键字参数
- 表示方法:【**变量名】
- 形参前使用**符号,表示可以接收多个关键字参数
- 收集的实参名称和值组成一个字典
- 注意:
- 可变关键字参数,必须要在所有参数最后面(即,参数,可变参数,可变位置参数)
- 例如:
##可变关键字传参定义 def showname(**kwargs): for k,v in kwargs.items(): print("{}:{}".format(k,v)) ##调用 showname(a=1,b=2,c=3)
混合定义
-
混合使用参数的时候,可变关键字参数要放到参数列表的最后,普通参数需要放到参数列表的前面,关键字参数 要放在可变位置参数和可变关键字参数之间
-
混合定义参数顺序:普通参数,缺省值参数,可变位置参数,关键字参数(keyword-only)(可带缺省值),可变关键字参数
-
混合定义参数传参顺序:位置参数在关键字参数前面
- 注意:混合传参时,可变关键字参数名称不能与普通参数名称相同。
#定义:函数如下: def showname(name,sex=6,*args,b=15,c,**kwargs): print("name={}\nsex={}\nargs={}\nb={}\nc={}\nkwargs={}".format(name,sex,args,b,c,kwargs)) ##调用 showname(1,5,4,5,b=23,c=34,hh=23)
- 其中name,set为普通参数,其中sex带默认值6,name在接受值时优先级最高
- args为可变参数,会收集函数中除了name和set外(即从第三个普通参数开始)其余剩下的普通参数(不包括关键字参数)
- b为带默认值的关键字参数,c为必须传入的关键字参数
- kwargs为可变关键字参数,会收集除key不在{a,b}内的所有关键字参数。
- 例如:
参数定义中的特殊符号:
- 例如:def app(*,a,b) 其中 * 号没有特殊意义,但这种方式标识了a,b必须是关键字参数
- 【*】星号之后的普通形参都变成了keyword-Only(关键字参数)
def add(*,x=4,y): print(x+y) add(x=3,y=9)
参数解构
-
给函数提供实参的时候,可以在集合类型前使用*或者**,把集合类型的解构解开,提取所有元素作为函数的实参
-
非字典类型使用*解构成位置参数
-
字典类型使用**解构成关键字参数
-
提取出来的元素数目要和参数的要求匹配,也要和参数的类型匹配
- 例如:add(**{“x”:4,“y”:5}) #等价于add(x=4,y=5),其中两个星号表示两层解构
def add(x,y): return x+y add(*[4,5]) #相当于add(4,5) add(*{'a':4,'b':5}) #相当于add("a","b") add(**{"x":4,"y":5}) #等价于add(x=4,y=5),其中两个星号表示两层解构 ##add(**{'a':4,'b':5})# 等价于add(a=4,b=5),由于没有找到对应的参数x,y所以会出错
-
总结
- 有位置可变参数和关键字可变参数
- 位置可变参数在形参前使用一个星号*,关键字可变参数使用两个星号**
- 位置可变参数和关键字可变参数都可以手机若干个实参,位置可变参数收集形成一个tuple,关键字可变参数收集成一个dict
- 混合使用参数的时候,可变关键字参数要放到参数列表的最后,普通参数需要放到参数列表的前面,关键字参数 要放在可变位置参数和可变关键字参数之间
- 混合定义参数传参顺序:位置参数在关键字参数前面
函数的返回值
- Python函数使用return语句返回“返回值”
- 所有函数都有返回值,如果没有return语句,隐试调用return None
- return语句并不一定是函数语句块中最后一条语句
- 一个函数可以存在多个return语句,单是只有一条可以被执行。如果没有一条return语句被执行到,就会隐试调用return None
- 如果函数执行了return语句,函数就会返回,当被执行的return语句之后的其他语句就不会被执行了
- return的作用:结束函数调用、返回“返回值”
- return 不能同时返回多个值。
def showvalue():
return 1,2,3 #这里实际返回的是将1,2,3封装成一个元组tuple
函数的作用域
- 每一个函数都会开辟一个作用域
- 作用域:一个标识符的可见范围,这就是标识符的作用域。一般常说的是变量的作用域
- 全局作用域
- 在整个程序的运行环境中都可见
- 全局作用域中的变量称为全局变量
- 使用关键字global 标明的变量,表示该变量为全局变量。(注意:声明全局变量后,如果直接对全局变量做运算,必须要保证外部全局变量中该变量已经赋值了)
- 全局变量简单示例:
x = 4 #x为全局变量 y = 5 def fun(): #y += 5 #注意:如果局部变量中要想改变全局变量,必须先使用global先标识改变量为全局变量。 y = 9 #此时y为fun的局部变量(赋值即定义) print(x) #x为全局变量,函数fun可以使用全局变量x return None fun()
- 局部作用域(或本地作用域)
- 在函数、类等内部可见
- 局部作用域中的变量称为局部作用域,其使用范围不能超过其所在局部作用域
- 如果函数中出现了对某一个变量赋值,那么该变量就会变为本函数的局部变量。(即:赋值即定义)
- 使用nonloacl标识的变量为外层函数的局部变量,但不能在全局作用域中定义。
- 例如:
x = 5 #x为全局变量 def fun2(): y = 9 #y为局部变量,作用域为fun2 print(y) print(x)
- 函数的默认值作用域
- defaults 记录了函数**普通参数(位置参数)**中的默认值。是个元组tuple,不会因为在函数体内改变了局部变量(形参)的值而发生改变
- kvdefaults 记录了函数**keyword_only(关键字参数)**中的默认值,是一个字典
# def fun(gdy=[]): #由于位置参数默认值为引用类型,所以gdy实际表示为[]在内存中的地址。所以gdy每次默认获取的也是[]在内存中的存放地址 # gdy.append(1) # return gdy # print(fun(),fun.__defaults__,id(fun.__defaults__),sep="\t\t") # print(fun(),fun.__defaults__,id(fun.__defaults__),sep="\t\t") # print(fun(),fun.__defaults__,id(fun.__defaults__),sep="\t\t") def fun(a=1,b=[],*,c=3,d=[]): #由于关键字参数默认值为引用类型,所以d关键存储的实际是[]在内存中所在的地址 a = 0 b.append(1) c = 0 d.append(1) return a,b,c,d print(fun(),fun.__defaults__,id(fun.__defaults__),fun.__kwdefaults__,id(fun.__kwdefaults__),sep="\t") print(fun(),fun.__defaults__,id(fun.__defaults__),fun.__kwdefaults__,id(fun.__kwdefaults__),sep="\t") print(fun(),fun.__defaults__,id(fun.__defaults__),fun.__kwdefaults__,id(fun.__kwdefaults__),sep="\t")
- 注意如下特殊示例
def x(a=[],b="ab",c={3,5},d=(1,)): a += [5] # 注意 a += [5] 等价于 a.extend([5]) b += "cd" d += (2,) # 注意:元组tuple和字符串都是不可变类型 #c += {4,6} #报错,集合不能这样用 print(x.__defaults__) x() x() print(x.__defaults__)
函数嵌套:
- 在一个函数中定义了另外一个函数,内函数能访问外函数的变量,而外部函数不能访问内部函数的变量。
- 简单示例:
x = 5 #x为全局变量 def fun1(): y = 4 #y为fun1的局部变量 def fun2(): ##fun2为fun1的内部函数 k = 6 #k为fun2的局部变量 return y #将外部函数变量y作为返回值。(内部函数可以访问外部函数中的变量) return fun2 #这里将内部函数对象在内存中的地址作为fun1的返回值。 m = fun1() #调用fun1可以获取fun1的内部函数fun2 m() #执行fun1的内部函数
global语句
- global标明变量为全局作用域的变量。
- global的使用原则:
- 外部作用域变量会在内部作用域可见,最好不要再内部作用域中直接改变其值,因为函数的目的就是为了封装,尽量与外界隔离
- 如果函数需要使用外部变量,应尽量使用函数的形参定义,并在调用传实参解决
- 简单示例:
x = 5 #赋值即定义全局变量 #由于在最外面,此时定义的变量都是全局变量 y = 4 #赋值即定义全局变量 def fun(): global x #表示在fun作用域内x为全局变量 def fun2(): global y #表示在fun2作用域内y也是全局变量 y +=1 ###注意:如果没有上门global语句定义y的作用域为全局变量,那么直接使用y+=1,由于是赋值语句,系统会认为y为fun2的局部变量。而y+=1此时等价于y = y+1 则在fun2的局部变量中y还没有赋值,即无法进行加减。所以没有上门global y会报错。 x = 9 #次数的x为fun2的局部变量 return None def fun3(): x = 1 #赋值即定义,此时x为fun3的局部变量(本地变量) print(y) #由于fun3内没有y的定义(赋值即定义),编译器会向上查找,直到找到最近作用域的变量y打印 print(x) #次数x为fun3作用域的变量x return None fun2() fun3() return None fun()
- global的使用原则:
闭包
- 内层函数用到了外层函数的局部变量(本地变量,或自由变量).称此现象为闭包
- 即:自由变量:未在本地作用域中定义的变量。例如定义在内层函数外的外层函数的作用域中的变量
- 当内层函数没有消亡,即闭包对应的变量也不会消亡
- 可以使用nonlocal标识的变量,为外层函数中的局部变量。但不能在全局作用域中定义。
- 简单示例:
函数counter中的内建函数inc引用了外层函数中的局部变量c所有构成了闭包。
def counter():
c = [0] #注意此时c为引用地址,引用地址指向[0]
def inc():
c[0] += 1 # 注意,此时c引用地址,引用地址指向[0]位置,并操作了[0]
return c[0]
return inc #返回内建函数inc
m = counter() #调用counter,获取内建函数,此时m指向inc函数所在的对象
print(m(),m())
c = 100 #此c为全局变量
print(m())
nonlocal语句
- nonlocal:将变量标记为在外层函数中定义的局部变量。但不能在全局作用域中定义
- 使用nonlocal可以简单的构成闭包
- 例如:
b = 10 #全局作用域中定义的变量为全局变量
def counter():
#nonlocal b #无法使用,因为b为全局作用域中的全局变量
#nonlocal bb #也无法使用,应为counter作用域外就是全局作用域。而全局作用域中定义的变量都是全局变量
c = 0
def inc():
nonlocal c
c +=1
return c
return inc
m = counter() #调用counter,获取内建函数,此时m指向inc函数所在的对象
print(m(),m())
c = 100 #此c为全局变量
print(m())
变量名解析原则LEGB
- Local,本地作用域、局部作用域的local命名空间。函数调用时创建,调用结束消亡
- Enclosing,Python2.2时引入了嵌套函数,实现了闭包,这个就是嵌套函数的外部函数的命名空间
- Global,全局作用域,即一个模块的命名空间。模块被import时创建,解析器退出时消亡
- Build-in,内置模块的命名空间,生命周期从python解释器启动时创建到解析器退出时消亡。例如print(open),print和open都是内置的变量
所以,一个名词(即变量名)查找顺序就是LEGB
函数的销毁
- 定义一个函数就是生产一个函数对象,函数名指向的就是函数对象
- 可以使用del语句删除函数,使其引用计数减1
- 可以使用同名标识符覆盖原有定义,本质上也是使其引用计数减1
- Python程序结束时,所有对象都会销毁
- 函数也是对象。是否销毁,还是看引用计数是否减为0