函数可以重复的实现某个功能,减少代码冗余;可以将不同功能的代码进行封装,简化代码结构,提高代码可读性。
函数
函数的打包和调用
>>> def myfunc():
pass
>>> myfunc()
>>> def myfunc():
for i in range(3):
print("I LOVE")
>>> myfunc()
I LOVE
I LOVE
I LOVE
def myfunc(name):
for i in range(2):
print("I Love {}".format(name))
>>> myfunc("Pyhton")
I Love Pyhton
I Love Pyhton
>>>
>>> def myfunc(name,time):
for i in range(time):
print("I love {}".format(name))
>>> myfunc("Pyhotn",4)
I love Pyhotn
I love Pyhotn
I love Pyhotn
I love Pyhotn
函数的返回值
def div(x,y):
return x/y
>>> div(4,2)
2.0
>>> def div(x,y):
if y==0:
return "被除数不能为零"
else:
return x/y
>>> div(2,0)
'被除数不能为零'
>>> div(6,3)
2.0
如果一个函数没有通过return来显示的返回内容,那么他也会自己在执行完函数体中的所有语句之后,返回一个None值
def myfunc():
pass
>>> print(myfunc())
None
函数的实参和形参
定义函数时的参数为形参,调用函数时传入的值为实参
函数的参数
参数就是接口,只要按照函数的要求将指定的参数正确的传递进去,函数就可以返回正确的结果。Python有多种参数。
位置参数
根据形参定义的位置传递实参
def myfunc(s,vt,o):
return "".join((o,vt,s))
>>> myfunc("我","爱","中国")
'中国爱我'
>>> myfunc("中国","爱","我")
'我爱中国'
关键字参数
根据参数的名字(形参名)传递参数进去,位置顺序可以忽略。
>>> def myfunc(s,vt,o):
return "".join((o,vt,s))
>>> myfunc(o="我",vt="打了",s="小甲鱼")
'我打了小甲鱼'
== 位置参数必须在关键字参数之前,否则会报错==
>>> myfunc(o="我","清蒸","xiaojiayu")
SyntaxError: positional argument follows keyword argument
>>> myfunc("清蒸","鱼",o="我")
'我鱼清蒸'
默认参数
函数定义时给形参赋一个值,如果调用函数时没有给这个参数传递实参进去,就默认使用定义时赋的值。
>>> def myfunc(s,vt,o="yu"):
return "".join((o,vt,s))
>>> myfunc("香蕉","吃")
'yu吃香蕉'
使用help查看函数文档
使用help()函数来查看函数文档时,经常会在函数的原型中看到一个斜杠(’ / ‘),斜杠左侧的参数必须传递位置参数,而不能使用关键字,星号(’ * ')表示左侧既可以是位置参数,也可以是关键字参数。
>>> help(abs)
Help on built-in function abs in module builtins:
abs(x, /)
Return the absolute value of the argument.
>>> help(sum)
Help on built-in function sum in module builtins:
sum(iterable, /, start=0)
Return the sum of a 'start' value (default: 0) plus an iterable of numbers
When the iterable is empty, return the start value.
This function is intended specifically for use with numeric values and may
reject non-numeric types.
>>> abs(-1.5)
1.5
>>> abs(x=-1.5)
Traceback (most recent call last):
File "<pyshell#57>", line 1, in <module>
abs(x=-1.5)
TypeError: abs() takes no keyword arguments
>>> sum([1,2,3],start=4)
10
>>> def abc(a,/,b,c):
print(a,b,c)
>>> abc(3,b=4,c=8)
3 4 8
自己也可以这样定义函数以及参数
def abc(a,*,b,c):
print(a,b,c)
>>> abc(1,b=2,c=4)
1 2 4
>>> abc(a=1,b=2,c=4)
1 2 4
>>> print("我","爱","Python")
我 爱 Python
>>> def myfunc(*args):
print("有{}个参数".format(len(args)))
print("第2个参数是,{}".format(args[1]))
>>> myfunc("小甲鱼","不二入市")
有2个参数
第2个参数是,不二入市
>>> myfunc(1,2,3,4,5)
有5个参数
第2个参数是,2
收集参数
参数的打包
- 打包为元组
函数不知道用户需要传入多少个参数,比如print()函数可以支持参数可多可少的情况,拥有这种特性的形参称为收集参数。它的本质是元组,使用元组打包和解包的特性。
>>> def myfunc(*args):
print(type(args))
>>> myfunc()
<class 'tuple'>
如果在收集参数之后还需要指定其他参数,那么在调用函数是,就应该使用关键字参数来指定后面的参数,否则Python就会把实参纳入到收集参数中:
>>> def myfunc(*args,a,b):
print(args,a,b)
>>> myfunc(1,2,3,4,5)
Traceback (most recent call last):
File "<pyshell#103>", line 1, in <module>
myfunc(1,2,3,4,5)
TypeError: myfunc() missing 2 required keyword-only arguments: 'a' and 'b'
>>> myfunc(1,2,3,a=4,b=5)
(1, 2, 3) 4 5
- 打包为字典
除了上面的将实参打包为元组,还可以将实参打包为字典
def myfunc(**kwargs):
print(kwargs)
>>> myfunc(a=1,b=2,c=3)
{'a': 1, 'b': 2, 'c': 3}
- 还可以将其混合起来,既包含元组也包含字典,如字符串的format方法
>>> def myfunc(a,*b,**c):
print(a,b,c)
>>> myfunc(1,(1,2,3),c="Pyhon")
1 ((1, 2, 3),) {'c': 'Pyhon'}
>>> myfunc(1,2,3,4,5,x=5,y=6)
1 (2, 3, 4, 5) {'x': 5, 'y': 6}
>>> help(str.format)
Help on method_descriptor:
format(...)
S.format(*args, **kwargs) -> str
Return a formatted version of S, using substitutions from args and kwargs.
The substitutions are identified by braces ('{' and '}').
参数的解包
在形参上使用称为参数的打包,在实参上使用称为解包
args=(1,2,3,4)
>>> def myfunc(a,b,c,d):
print(a,b,c,d)
>>> myfunc(args)
Traceback (most recent call last):
File "<pyshell#125>", line 1, in <module>
myfunc(args)
TypeError: myfunc() missing 3 required positional arguments: 'b', 'c', and 'd'
>>> myfunc(*args) //解包为元组
1 2 3 4
>>> kwargs={'a':1,'b':2,'c':3,'d':4}
>>> myfunc(kwargs)
Traceback (most recent call last):
File "<pyshell#128>", line 1, in <module>
myfunc(kwargs)
TypeError: myfunc() missing 3 required positional arguments: 'b', 'c', and 'd'
>>> myfunc(**kwargs) //解包为字典
1 2 3 4
作用域
作用域是指一个函数可以被访问的范围,由代码中被赋值的位置来决定的
局部作用域
若一个变量被赋值在函数的内部,作用域仅限于该函数中,称为局部变量
全局作用域
若在函数中存在一个跟全局一模一样的局部变量,该全局变量在函数内部可以访问但是不可以修改它的值。
>>> def myfunc():
return 1,2,3
>>> myfunc()
(1, 2, 3)
>>> x,y,z=myfunc()
>>> x
1
>>> y
2
>>> z
3
>>> def myfunc():
x=520
print(x)
>>> myfunc()
520
>>> print(x)
1
>>> id(x)
1798563588400
>>> def myfunc():
x=520
print(id(x))
>>> myfunc()
1798603569936
若要在函数内部修改全局变量的值,需要使用global进行声明
>>> def myfunc():
global x
x = 520
print (x)
>>> myfunc()
520
嵌套函数
调用内部函数只能通过外部函数来调用
>>> def A():
x=520
def funB():
x = 880
print("in funB,x={}".format(x))
print("in funA,x=",x)
>>> A()
in funA,x= 520
>>> def A():
x=520
def funB():
x = 880
print("in funB,x={}".format(x))
funB()
print("in funA,x=",x)
>>> A()
in funB,x=880
in funA,x= 520
在内部函数中修改外部函数的变量时需要使用nonlocal进行声明
>>> def A():
x=520
def funB():
nonlocal x
x = 880
print("in funB,x={}".format(x))
funB()
print("in funA,x=",x)
>>> A()
in funB,x=880
in funA,x= 880
LEGB规则
英文名称 | 作用域范围 | 注释/说明 | |
---|---|---|---|
L | Local | 局部作用域 | ———— |
E | Enclosed | 嵌套函数的外层函数作用域 | 在嵌套函数中,局部作用域会覆盖外层函数的作用域,需要使用nonlocal进行声明 |
G | Global | 全局作用域 | 当局部作用域与全局作用域发生冲突时,Python会使用局部作用域的变量,除非使用Global进行声明 |
B | Build In | 内置作用域 | 避免使用Python的BIF内置函数作为变量名 |
闭包
在嵌套函数中,有两种方式可以调用内部函数:
- 通过外部函数来调用内部函数
>>> def A():
x=520
def funB():
nonlocal x
x = 880
print("in funB,x={}".format(x))
funB()
print("in funA,x=",x)
>>> A() //通过外部函数来调用内部函数
in funB,x=880
in funA,x= 880
- 不通过外部函数来调用内部函数(函数指针)
>>> def funA():
x=880
def funB():
print(x)
return funB
>>> funA()
<function funA.<locals>.funB at 0x000001A2C5232790> //得到一个funB的引用
>>> funA()()
880
>>> f=funA() //调用funA()将这个变量赋给一个函数
>>> f()
880
上面的代码出现了一个神奇的现象:外部函数中定义的变量在外部函数调用完之后就没有意义了(funA函数中定义的变量x在f=funA()语句执行完之后就没有意义了),但是还是可以通过调用内部函数打印它( x )的值。
对于嵌套函数来说,外层函数的作用域是会通过某种形式给保存下来的,尽管这个函数已经调用完了,但是外层作用域的变量会保存下来,不会像局部作用域一样调用完就消失了
定义
闭包 也称为工厂函数,根据上述现象利用嵌套函数来实现类似工厂的功能。
>>> def power(exp):
def exp_of(base):
return base ** exp
return exp_of
>>> square = power(2) //嵌套函数的外部作用域会被保存下来,在执行该语句时,square指向exp_of这个内部函数,这个函数就记住了外层函数作用域的参数exp=2
>>> cube = power(3) //cube变量指向exp_of这个函数,这个内部函数记住了exp=3
>>> square(2)
4
>>> square(5)
25
>>> cube(2)
8
>>> cube(5)
125
外部函数power相当于一个工厂,由于参数不同,得到了两条不同的生产线
- square 返回参数的平方
- cube 返回参数的立方
用法
使用nonlocal语句可以让嵌套函数的内层函数修改外层函数的变量
>>> def outer():
x = 0
y = 0
def inner(x1,y1):
nonlocal x,y
x += x1
y += y1
print(f"现在,x = {x}, y = {y}")
return inner
>>> move = outer()
>>> move(1,2)
现在,x = 1, y = 2
>>> move(-2,2)
现在,x = -1, y = 4
利用内层函数能够记住外层函数作用域这一特性,且使用nonlocal语句,让内层函数修改外层函数作用域中的变量,实现了带记忆功能的函数。
闭包的两个特性
- 利用嵌套函数的外层作用域具有记忆功能的特性,让数据保存在外层函数的参数或者变量中
- 将内层函数作为返回值返回,从外部函数间接调用内层函数
装饰器
既然函数可以作为返回值返回,那么是否可以把函数作为参数传递给另一个函数
>>> import time
>>> def time_master(func):
print("开始运行程序")
start=time.time()
func()
stop=time.time()
print("程序结束运行")
print(f"一共耗费了{(stop-start):.2f}秒。")
>>>
>>> def myfunc():
time.sleep(2)
print("Hello World")
>>> time_master(myfunc) //将函数作为参数传递给另一个函数
开始运行程序
Hello World
程序结束运行
一共耗费了2.04秒。
可是如果每次求一个函数的运行时长都要显示的调用time_master函数显然不是最优的解决方案。
改进方案:通过使用装饰器,实现调用my_func()函数时,能够自觉的执行time_master()函数
在代码没有显示的调用time_master()函数,但是运用结果与调用的相同
装饰器在实际开发场景中的应用
插入日志、添加性能测试或要求先进行权限验证是,就可以通过装饰器在不修改原代码的情况下实现这些功能。
装饰器的原理和本质
装饰器的本质是闭包,将函数作为一个参数,类似于语法糖(fstring中提过,语法糖为某种特殊的语法,对函数的功能本身无影响,但是更加简洁)
多个装饰器可以用在同一个函数上
代码如下:
//加1
def add(func):
def inner():
x=func() //令x=64,返回64+1
return x+1
return inner
//立方
def cube(func):
def inner():
x=func() //令x=4,返回4的立方
return x*x*x
return inner
//平方
def square(func):
def inner():
x=func()
return x*x
return inner
@add
@cube
@square
def test():
return 2 //装饰器由内向外执行,将test作为参数塞到square这个装饰器中运行,令x=2,返回2的平方
print(test())
代码执行结果如下:
给装饰器传递参数
多加一层函数嵌套,添加一次调用,通过这次调用将参数传递进去