函数
python函数
- 由若干语句组成的语句块、函数名称、参数列表构成,它是组织代码的最小单元
- 完成一定的功能
- 可调用函数 callable
函数的作用
- 结构化编程对代码的最基本的封装,一般按照功能组织一段代码
- 封装的目的 为了复用 ,减少冗余代码
- 代码更加简洁美观,可读易懂
函数的分类
- 内建函数 : 如max(), reversed()
- 库函数 : 如math
- 自定义函数: 使用def关键字定义
函数定义
def 函数名(参数列表):
函数体(代码块)
[return 返回值]
- 函数名就是标识符,命名要求一样
- 语句块必须缩进,约定4个空格
- python的函数若没有return语句,会隐式的返回一个None值
- 定义中的参数列表称为形式参数,只是一种符号表达(标识符),简称形参
函数调用
-
函数定义,只是声明了一个函数,它不能被执行,需要调用执行
-
调用的方式,就是函数名后加小括号,如有必要在括号内填写上参数
-
调用时写的参数是实际参数,是实实在在传入的值,简称实参
def add(x,y): # 函数定义
result = x+y # 函数体
return result # 返回值
out = add(4,5) # 函数调用,可能有返回值,使用变量接收这个返回值
print(out) # print函数加上括号也是调用
9
函数参数
函数在定义时要约定好形式参数,调用时也提供足够的实际参数,一般来说,形参和实参个数要一致(可变参数除外)
传参方式
- 位置传参
- 定义时def fn(x,y,z),调用使用fn(1,3,5),按照参数定义顺序传入实参
- 关键字传参
- 定义时def fn(x,y,z),调用使用fn(x=1,y=3,z=5),使用形参的名字来传入实参的方式,如果使用了形参名字,那么传参顺序就可和定义顺序不同
要求位置参数必须在关键字参数之前传入,位置参数是按照位置对应的
def fn(x,y,z):
pass
fn(1, 3, z=5)
fn(z = None, y=10, x=[1])
fn((1,), z=6, y=4.1)
fn(y=5, z=6, 2) #错误
参数缺省值
缺省值也称为默认值,可以在函数定义时,为形参增加一个缺省值
作用:
- 参数的默认值可以在未传入足够的实参的时候,对没有给定的参数赋值为默认值
- 参数非常多的时候,并不需要用户每次都输入所有的参数,简化函数的调用
可变参数
-
可变位置参数:def 函数名(* 形参):
-
只接受位置传参,不接受关键字传参
-
*args
-
将收集到的实参组织到一个tuple中
def sun(*iterable): print(type(iterable)) s = 0 for x in iterable: s += x return s sum() ----> <class 'tuple'> 0 sum(1,2,3) ----> <class 'tuple'> 6 sum(range(5)) ----> <class 'tuple'> TypeError: unsupported operand type(s) for +=: 'int' and 'range'
-
-
可变关键字参数:def函数名(**形参):
- 只接受关键字传参,不接受位置传参
- **kwargs
- 将收集到的实参组织到一个dict中
- 关键字任意定义
def test(**kwargs): print(kwargs) kwargs['c'] = 1000 print(kwargs) test(),test(a = 1,b = 20),test(c = 40) --->{'c': 1000} {'a': 1, 'b': 20, 'c': 1000} {'c': 1000}
keyword-only参数
keyword-only 参数: 在形参定义时,在一个*之后,或一个可变位置参数(*args)之后,出现的普通参数,就已经不是普通的参数了,称为keyword-only参数
def fn(*args,x):
print(args,x)
fn(3,5) # ------》TypeError
fn(3,5,7) # ------》TypeError
fn(3,5,x=7) # ------》(3, 5) 7
# x就是keyword-only 参数 ,必须使用关键字传参的方式
# 可以认为args可变位置参数已经截获了所有的位置参数,其后的变量x 不可能通过位置传参传入了
keyword - only 参数另一种形式:
*后所有的普通参数都变成了keyword - only 参数
def fn(*,x, y):
print(x,y)
fn(x=6, y=7) # ------》正确
fn(y=3, x=8) # ------》正确
参数的混合使用
一般顺序: 普通参数、缺省值参数、可变位置参数、keyword-only 参数(可带缺省值)、可变关键字参数
a, b , c = 200 , *args , d , e = 300 , **kwargs
**kwargs之后不允许出现任何参数
*args之后可以跟keyword-only参数
混合使用时,普通参数需要放在参数列表前面,
参数解构
- 在给函数提供实参的时候,可以在可迭代对象之前使用*或者**来进行结构的解构,提取出其中所有元素作为函数的实参
- 使用* 解构成位置传参
- 使用** 解构成关键字传参
- 提取出来的元素数目要和参数的要求匹配
def add(x,y):
print(x,y)
return x+y
add(4,5)
add((4,5))
t=4,5
add(t[0],t[1])
add(*t)
add(*(4,5))
add(*[4,5])
add(*{4,5})
add(*range(4,6))
add(*{'a':10,'b':13}) #----》a b 'ab'
add(**{'a':10,'b':13}) #---->TypeError: add() got an unexpected keyword argument 'a'
add(**{'x':10,'y':13}) #--->10 13 23
def add(*iterable):
result = 0
for x in iterable:
result += x
return result
add(1,2,3) #----->6
add(*[1,3,5]) #----->9
add(*range(5)) #----->10
函数返回值
-
python函数使用return语句返回“返回值”
-
所有函数都有返回值,如果没有return语句,隐式调用 return None
-
一个函数可以存在多个return语句,但是只要有一条可以执行。如果没有一条return语句被执行到,隐式调用return None
-
如果有必要,可以显示调用return None,可以简写成return
-
如果函数执行了return函数,函数就会返回,当前被执行的return语句之后的其他语句就不会被执行了
-
返回值的作用:结束函数调用,返回“返回值”
-
函数不能同时返回多个值
def fn(): return 1,3,5 #看似返回多个值,其实隐式的被python封装成了一个元祖 fn() (1, 3, 5)
函数作用域***
作用域
一个标识符的可见范围,这就是标识符的作用域,一般常说的是变量的作用域
函数会开辟一个作用域,X变量被限制在这个作用域中,所以在函数外部 X 变量不可见
作用与分类
- 全局作用域
- 在整个程序运行环境中都可见
- 全局作用域中的变量称为全局变量
- 局部作用域
- 在函数、类等内部可见
- 局部作用域中的变量称为局部变量,其使用范围不能超过其所在的局部作用域
- 也称为本地作用域
#局部变量
def fn1():
x = 1
def fn2():
print(x) #不可打印
print(x) # 不可打印
#全局变量
x = 5
def fn():
print(x) #可见
fn() #----> 5
一般来说,外部作用域变量 可以在函数内部可见,可以使用
反过来,函数内部的局部变量,不能在函数外部看到
函数嵌套
在一个函数中定义另一个函数
def outer ():
def inner():
print("inner")
print("outer")
inner()
outer() #-----> outer inner
inner() #----->NameError: name 'inner' is not defined
内部函数inner不能在外部被直接使用,会抛异常NameError,因为它在函数外部不可见
其实inner就是一个标识符,就是一个函数outer内部定义的变量而已
嵌套结构的作用域
def outer ():
o = 65
def inner():
print("inner{}".format(o))
print(chr(o))
inner()
print("outer{}".format(o))
outer()
------>inner65
A
outer65
def outer ():
o = 65
def inner():
o = 97
print("inner{}".format(o))
print(chr(o))
inner()
print("outer{}".format(o))
outer()
----->inner97
a
outer65
- 外层变量在内部作用域可见
- 内层作用域inner中,如果定义了 o = 97 ,相当于在当前函数inner作用域中重新定义了一个新的变量o ,但是,这个o 并不能覆盖掉外部作用域outer2中的变量o 。只不过对于inner函数来说,其实能可见自己作用域中定义的变量o 了。
一个赋值语句的问题
函数1
x = 5
def foo():
print(x)
foo() #---->5
函数2
x = 5
def foo():
x +=1 # ----> 报错
print(x)
foo() #----->UnboundLocalError: local variable 'x' referenced before assignment
原因分析:
- x += 1 其实就是 x = x +1
- 只要有"x="出现,这就是赋值语句。相当于在foo内部定义了一个局部变量x, 那么foo内部所有x 都是这个局部变量x 了
- x = x +1 相当于使用了局部变量x ,但是这个x还没有完成赋值,就被右边拿来做加1 操作了
解决方案如下:global
global语句
x = 5
def foo():
global x #全局变量
x +=1
print(x)
foo() ----->6
- 使用global关键字的变量,将foo内的x 声明为使用外部的全局作用域中定义的x
- 全局作用域中必须有x 的定义
def foo():
global x
x +=1
print(x)
foo()
# NameError: name 'x' is not defined
def foo():
global x
x = 10
x +=1
print(x) # ----->11
foo()
print(x) # ----->11
使用global关键字定义的变量,虽然在foo函数中声明,但是这将告诉当前foo函数作用域,这个x变量将使用外部全局作用域中的x
即使是在foo中又写了 x = 10 ,也不会在foo这个局部作用域中定义局部变量x 了
使用global,foo中的x 不再是局部变量了,它是全局变量
总结
- x+=1 这种是特殊形式产生的错误的原因? 先引用后赋值,而python动态语言是赋值才定义,才能被引用。解决办法,在这条语句前增加z = 0,之类的赋值语句,或者使用global告诉内部作用域,去全局作用域中查找定义变量
- 内部作用域使用x = 10 之类的赋值语句会重新定义局部作用域使用的变量x ,但是,一旦这个作用域中使用global声明x 为全局的,那么x= 5 相当于在为全局作用域的变量x 赋值
global使用原则
- 外部作用域变量会在内部作用域可见,但也不要在这个内部的局部作用域中直接使用,因为函数的目的就是为了封装,尽量与外界隔离
- 如果函数需要使用外部全局变量,请尽量使用函数的形参定义,并在调用实参的时候解决
闭包***
- 自由变量:未在本地作用域中定义的变量。例如定义在内层函数外的外层函数的作用域中的变量。
- 闭包:就是一个概念,内部函数用到了外部函数的自由变量,形成闭包。
def fn():
x = 1 # x=1相当于fn1()的自由变量
def fn1():
y = x + 1 # 本行用到了自由变量,形成闭包
print(y)
fn1()
fn()
--->2
nonlocal语句
内层函数往外找,找临近的,但不能是在全局里找
非全局的,但又不是该函数本地的,嵌套函数专用
#nonlocal语句
def counter ():
count = 0
def inc():
nonlocal count # 声明变量count不是本地变量
count += 1
return count
return inc
foo = counter()
print(foo(),foo(),foo())
1 2 3
# count是外层函数的局部变量,被内部函数引用
# 内部函数使用nonlocal关键的声明count变量在上级作用域而非本地作用域中定义
# 代码中内层函数引用外部局部作用域中的自由变量,形成闭包
默认值的作用域
# 默认值的作用域
def foo(xyz = 1):
print(xyz)
foo() #---->1
foo() #---->1
print(xyz) #---->name 'xyz' is not defined因为这个print()是在函数之外,不能看到函数里边的变量
def foo(xxx = []):
xxx.append(1)
print(xxx)
foo() #---->[1]
foo() #---->[1,1]
foo() #---->[1,1,1]
print(xxx)#---->name 'xxx' is not defined因为这个print()是在函数之外,不能看到函数里边的变量
#为什么第二次调用foo函数打印的是[1,1]
# 1.因为函数也是对象,每个函数定义被执行之后,就生成了一个函数对象,和函数名这个标识符关联
# 2,函数是对象,有属性。python把函数的默认值放在了函数对象的属性中,这个属性就伴随着这个函数对象的整个生命周期
# 3.查看foo._defaults_属性,他是个元组
def foo (xyz=[],m=123,n='abc'):
xyz.append(1)
print(xyz)
foo() #--->[1]
print(id(foo),foo.__defaults__) #--->97936704 ([1,1], 123, 'abc')
foo() #--->[1,1]
print(id(foo),foo.__defaults__) #--->97936704 ([1,1], 123, 'abc')
foo() #--->[1,1,1]
print(id(foo),foo.__defaults__) #--->97936704 ([1,1,1], 123, 'abc')
# 函数地址并没有变,就是说foo这个函数对象的没有变过,调用它,它的属性_defaults_中使用元组保存默认值xyz默认值是引用类型就,;引用类型的元素变化,并不是元组的变化
# foo.__defaults__查看函数缺省值
#非引用类型缺省值
def foo(xxx,m=123,n='aaa'):
m=456
n='sss'
print(xxx)
print(foo.__defaults__) #--->(123, 'aaa')
foo('qqq') #--->qqq
print(foo.__defaults__) #--->(123, 'aaa')
# 属性__defaults__中使用元组保存所有位置参数默认值,它不会因为在函数体内改变了局部变量(形参)的值而发生改变
#keyword-only参数的缺省值
def foo(zzz,m=123,*,n='abc',t=[1,2]):
m=456
n='qqq'
t.append(xyz,m,n,t)
print(foo.__defaults__,foo.__kwdefauts__) #---->(123,) {'n': 'abc', 't': [1, 2]}
foo('qwer') #---->qwer 456 qqq [1, 2, 300]
print(foo.__defaults__,foo.__kwdefaults__) #---->(123,) {'n': 'abc', 't': [1, 2, 300]}
#属性__defaults__中使用元组保存所有位置参数默认值
#属性__kwdefaults__中使用字典保存所有keyward-only参数的默认值
def x(a=[]):
a += [5] #相当于a.extend([5]),就地修改
print(a)
print(x.__defaults__) #--->([],)
x() #--->[5]
x() #--->[5, 5]
print(x.__defaults__) #--->([5, 5],)
def y(a=[]):
a = a + [5]
print(a)
print(y.__defaults__)#--->([],)
y()#--->[5]
y()#--->[5]
print(y.__defaults__)#--->([],)
# 两者不同之处,都在第二行 a += [5] 与 a=a+[5]
# + 表示两个列表合并并返回了一个全新的列表
# += 表示 就地修改前一个列表,在其后面追加一个列表。相当于extend方法
变量名解析原则LEGB***
- Local,本地作用域、局部作用域的local命名空间。函数调用时创建,调用结束消亡
- Enclosing,Python2.2引入嵌套函数,实现了闭包,这个就是嵌套函数的外部函数的命名空间
- Global,全局作用域,即一个模块的命名空间。模块被import时创建,解释器退出时消亡
- Build-in,内置模块的命名空间,生命周期从python解释器启动时创建,到解释器退出时消亡
函数的销毁
- 定义一个函数就是生成一个函数对象,函数名指向的就是函数对象
- 可以使用del语句删除函数,使其引用计数减1
- 可以使用同名标识符覆盖原有定义,本质上也是使其引用计数减1
- python程序结束时,所有对象销毁
- 函数也是对象,也不例外,是否销毁,还是要看引用计数是否减为0
匿名函数
匿名:隐匿名字,即没有名称
匿名函数:没有名字的函数
lambda表达式
使用lambda表达式构建匿名函数
lambda x:x**2 #定义
(lambda x:x**2)(3) #调用
--->9
foo = lambda x,y:(x+y)**2
foo(1,2)
--->9
#等价于
def foo(x,y):
return(x+y)**2
foo(1,2)
-
使用lambda关键字定义匿名函数,格式为 lambda [参数列表] : 表达式
-
参数列表不需要小括号。无参就不写参数
-
冒号用来分割参数列表和表达式部分
-
不需要使用return。表达式的值,就是匿名函数的返回值。表达式中不能出现等号
-
lambda表达式(匿名函数)只能写在一行上,也称为单行函数
匿名函数往往用在为高阶函数传参时,使用lambda表达式,往往能够简化代码
#返回常量的函数
print((lambda :0)()) #---->0
#加法匿名函数,带缺省值
print((lambda x,y=3:x+y)(5)) #---->8
print((lambda x,y=3:x+y)(5,6)) #---->11
#keyword-only参数
print((lambda x,*,y=30: x+y)(5)) #---->35
print((lambda x,*,y=30: x+y)(5,y=10)) #---->15
#可变参数
print((lambda *args :(x for x in args))(*range(5))) #---->生成器对象<generator object <lambda>.<locals>.<genexpr> at 0x0602E270>
print((lambda *args :[x+1 for x in args])(*range(5))) #---->[1, 2, 3, 4, 5]
print((lambda *args :{x%2 for x in args})(*range(5))) #---->{0, 1}
递归函数
递归Recursion
- 函数直接或者间接调用自身就是递归
- 递归需要有边界值、递归前进段、递归返回段
- 当递归条件不满足时,递归前进
- 当边界条件满足时,递归返回
- 递归一定要有退出条件,递归调用一定要执行到这个退出条件。没有退出条件的递归调用,就是无限调用
- 递归调用的深度不宜过深
- python对递归调用的深度做了限制,以保护解释器
- 超过深度限制,抛出RecursionError
例子 : 斐波那契数列
-
斐波那契数列:1,1,2,3,5,8,13,21,34,····
-
如果设F(n) 为该数列的第n项,那么这句话可以写成如下形式:F(n)=F(n-1) +F(n-2)
-
F(0)=0,F(1)=1,F(n)=F(n-1) +F(n-2)
-
#循环 a = 0 b = 1 n = 5 for i in range(n-1): a,b = b,a+b else: print(b) #--->5
-
#递归 F(0)=0,F(1)=1,F(n)=F(n-1) +F(n-2) def fib(n): return 1 if n<3 else fib(n-1) + fib(n-2) fib(5) #--->5 #fib(5)解析: #fib(4)+fib(3) #fib(4) 调用 fib(3)+fib(2) #fib(3) 调用 fib(2)+fib(1) #fib(2) 、fib(1)是边界 return 1,所有函数调用逐层返回 #性能太低,重复计算的量太多
递归的性能
- 循环稍微复杂一些,但是只要不是死循环,可以多次迭代直至算出结果
- fib函数代码极简易懂,但是只能获取到最外层的函数调用,内部递归结果都是中间结果。而且递归次数太多,深度越深,效率越低
- 递归还有深度限制,如果递归复杂,函数反复压栈,栈内存很快就会溢出
def fib(n,a=0,b=1):
a,b=b,a+b
if n == 1:
return a
return fib(n-1,a,b)
fib(5)
#--->5
#上述代码与循环类似
#参数n是边界条件,用n来计数
#上一次的计算结果直接作为函数的实参
# 效率很高
间接递归
def foo1():
foo2()
def foo2():
foo1()
foo1()
#间接递归,是通过别的函数调用了函数自身
#但是,如果构成了循环递归调用是非常危险的,但是往往这种情况在代码复杂的情况下,还是可能发生的
n 1 if n<3 else fib(n-1) + fib(n-2)
fib(5)
#—>5
#fib(5)解析:
#fib(4)+fib(3)
#fib(4) 调用 fib(3)+fib(2)
#fib(3) 调用 fib(2)+fib(1)
#fib(2) 、fib(1)是边界 return 1,所有函数调用逐层返回
#性能太低,重复计算的量太多
## 递归的性能
- 循环稍微复杂一些,但是只要不是死循环,可以多次迭代直至算出结果
- fib函数代码极简易懂,但是只能获取到最外层的函数调用,内部递归结果都是中间结果。而且递归次数太多,深度越深,效率越低
- 递归还有深度限制,如果递归复杂,函数反复压栈,栈内存很快就会溢出
```python
def fib(n,a=0,b=1):
a,b=b,a+b
if n == 1:
return a
return fib(n-1,a,b)
fib(5)
#--->5
#上述代码与循环类似
#参数n是边界条件,用n来计数
#上一次的计算结果直接作为函数的实参
# 效率很高
@[toc]