函数
函数允许程序的控制在不同的代码片段之间切换,函数的重要意义在于可以在程序中清晰地分离不同的任务,将复杂的问题分解为几个相对简单的子问题,并逐个解决。即“分而治之”。
Python的自建模块一般体现为函数。Python函数有如下特点:
(1) 函数是组织好的、可重复使用的,用来实现单一或者相关联功能的代码段。
(2) 函数首先关注所有任务,然后关注如何完成每项任务。函数类型有两种:有返回值的函数和仅仅执行代码而不返回值的函数。
(3) 函数能提高应用程序的模块化程度和代码的重要性。
Python有很多内建函数(即内置函数)例如:print()、int()、float()等。但也可以自己创建函数,在python中成为用户自定义函数。
- 基本原理:
函数的定义:
(1) 语法: def 函数名(参数1,参数2,参数3,,,,):2
“描述信息”3
函数体4
return #用来定义返回值,可以跟任意数据类型<br><br>
(2)函数定义应该遵循的规则:
- 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号(),最后是冒号(:)
- 函数命名应该能够描述函数的功能,而且必须符合标识符的命名规则。
- 任何传入参数和自变量必须放在圆括号中间。圆括号之间可以用于定义参数。
- 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
- 函数内容(语句块)放于冒号后,每条语句都要缩进相应数量的空格。
return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None。
函数的调用:通过输入实参来替换形参完成函数的调用
定义时无参,调用时也无参(无参函数)
定义时有参,调用时需要传参(有参函数)
2.形参与实参
在定义函数时,它的输入变量被称为函数的形参,而执行函数时的输入变量被称为实参。
(1) 参数传递-----通过位置和关键字
例如:def subtract(x1,x2): #函数定义
return(x1-x2)
位置实参:在调用函数的时候,必须将每个实参都关联到函数定义的每一个形参,最简单的关联方式就是基于实参的顺序。
注意:使用位置实参的方式传值,传入的实参个数必须与形参相同,否则运行程序会报错。
通过位置传递参数来调用函数,当调用函数subtract时,每个形参都被实参所取代,只有实参的顺序是重要的,实参可以是任意对象。
z=3
e=subtract(5,z)
关键字实参:是通过关键字-值的方式,关键字实参的方式就不需要考虑函数调用过程中实参的顺序。同一个参数不能传两个值
z=3
e=subtract(x2=z,x1=5)
#在这里的函数调用中,实参时通过名称赋值给形参而不是通过位置
传参的规则:
在实参的角度:
规则:按位置传值必须在按关键字传值的前面
对一个形参只能赋值一次
1.按照位置传值
2.按照关键字传值
3.混着用
在形参的角度:
规则:默认参数必须放到位置参数的后面
1.位置参数
2.默认参数
3.*args (接收位置传值)
4.**kwargs(接收关键字传值)
(2) 更改实参
实参的作用是为函数提供必要的输入数据,更改函数内部的参数值通常不会影响函数外部的实参值
例如1:对于所有不可变参数(字符串、数字和元组)更改函数内部的实参值通常不会影响函数外部的实参值。
def subtract(x1,x2):
z=x1-x2
x2=50.
return (z)
a=20.
b=subtract(10,a) #返回-10
print(b)
print(a) #返回20.0
示例2:将可变参数(例如:列表或字典)传递给函数并在函数内部将其改变,那么函数外部也会发生改变
def subtract(x):
z=x[0]-x[1]
x[1]=50.
return(z)
a=[10,20]
b=subtract(a)
print(b) #返回-10
print(a) #返回[10,50.0]
(3) 默认参数
默认值是定义函数时已经给出的值。如果在不提供该参数的情况下调用函数,python将使用程序员在定义函数时所提供的值。
def subtract(x1,x2=0):
z=x1-x2
return (z)
x=subtract(10)
print(x) #必须给出所有的位置参数,只要那些省略的参数在函数定义中有默认值,就不必提供所有的关键字参数。
注意:可变默认参数:使用可变数据类型的参数作为默认参数时,如果更改函数内部的可变类型参数,则会产生副作用。例如:
def my_list(x1,x2=[]):
x2.append(x1)
return(x2)
print(my_list(1)) #结果为:[1]
print(my_list(2)) #结果为[1,2]
(4) 可变参数:传入的参数的个数是可变的。
*args 位置参数,表示把args这个list(列表)或者tuple(元组)的所有元素作为可变参数传进去
def foo(x,*args): #x为位置参数, args是可变参数
print(x)
print(args)
foo(1,2,3,4) #1传给位置参数x,剩下的全部传给args
foo(1,*(2,3,4)) #可以直接把一个tupleh或list传给可变参数args
返回结果为:1
(2, 3, 4)
**kwargs关键字参数:允许传入0个或者任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict(字典)。
示例:
def foo(x,**kwargs):
print(x)
print(kwargs)
#两种传递方式:**传递或者键值对传递
foo(1,**{"y":2,"z":3}) #可以直接把一个字典通过加**传递给关键字参数,
# 返回 1
{'y': 2, 'z': 3}
foo(1,y=2,z=3) #返回结果同上
列表和字典可用来定义或调用参数个数可变的函数。
例如:
import matplotlib.pyplot as plt
data=[[1,2],[3,4]]
style=dict({'linewidth':3,'marker':'o','color':'green'})
plt.plot(*data,**style) #以*为前缀的变量名称(*data)是指提供了在函数调用中解包的列表,这样一来,列表就会产生位置参数。以**为前缀的变量名称(**style)是将字典解包为关键字参数。
3. 返回值
return[表达式]用于退出函数。Python中的函数总是返回单个对象。如果一个函数必须返回多个对象,那么这些对象将被打包并作为一个元组对象返回。
示例:import numpy as np
def complex_to_polar(z):
r = np.sqrt(z.real**2+z.imag**2)
phi = np.arctan2(z.imag,z.real)
return (r,phi)
z=3+5j
a=complex_to_polar(z)
r=a[0]
phi=a[1] #可写为一行:r,phi=complex_to_polar(z)
print(r) 运行结果为:5.830951894845301
1.0303768265243125
print(phi)
如果函数没有return语句,则返回None。因为由于传递给函数的变量可能会有所修改,则在很多情况下,函数不需要返回任何值。
示例:def append_to_list(L,x):
- append(x) #注意:该函数没有返回值,是由于它修改了给出的参数对象中的其中一个L。
这里仅提到了列表方法,如append、extend、reverse、sort方法不返回任何值(返回None),当通过这种方法来修改对象时,修改被称为原位修改。
4. 递归函数
在一个函数内部,可以调用其他函数。假如一个函数在其内部可以调用自己,那么这个函数是递归函数。
递归是一种直接和间接地调用函数自身的过程。递归的特性有三点:
(1)必须有明确的结束条件。
(2)每次进入更深一层的递归时,问题规模相比上次递归应有所减少。
(3)递归效率不高,递归层次过多会导致栈溢出。
递归的优点与缺点:
优点:递归使代码看起来更加整洁、优雅;可以用递归将复杂任务分解成更加简单的子问题;
使用递归比使用一些嵌套迭代更加容易。
缺点:递归的逻辑很难调试、跟进;递归调用的代价高昂(效率低)。
例如:def recursion():
return(recursion()) #注意:这个递归定义显然什么都没有,如果运行该函数的结果就是一段时间后程序就崩掉了。因此每次调用函数都将会消耗一些内存,当内存爆满就自然就挂了。这个函数中的递归为无穷递归,就好比一个while死循环。
正常的递归函数应该包含以下两个部分:
基线条件(针对最小问题):满足条件时函数将直接返回一个值
递归条件:包含一个或者多个调用,这些调用旨在解决问题的一部分。
示例:
#用传统的循环方式写:
def factorial(n):
result = n
for i in range(1,n):
result *= i
return result
print(factorial(2))
#通过递归的方式实现的,n的阶乘看做是n乘以(n-1)的阶乘,而1 的阶乘为1
def factorial(n):
if n == 1:
return 1
else:
return n*factorial(n-1)
print(factorial(2))
尾递归:
在计算机中,函数调用是通过栈这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧;每当函数返回,栈就会减少一层栈帧。由于栈的大小不是无限的,所以递归调用的次数越多会导致栈溢出。)
为了防止栈的溢出;我们可以使用尾递归优化,尾递归是指:在函数返回的时候,调用自身本身,并且return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况,尾递归的实现方式是 :使函数本身返回的是函数本身。
示例如下:
def chebyshev(n,x):
if n==0:
return(1.)
elif n==1:
return(x)
else:
return(2*x*chebyshev(n-1,x)-chebyshev(n-2,x))
a=chebyshev(5,0.52)
print(a) #返回结果:0.39616645119999994
5. 函数文档、函数是对象、偏函数应用
(1) 文档字符串:在使用def 关键字定义一个函数时, 其后必须跟有函数名和包括形式参数的圆括号。函数体的下一行开始,必须是缩进的。函数体的第一个的逻辑行的字符串,这个字符串就是这个函数的文档字符串,通常称作docstring
文档字符串的定义:
在函数体的第一行,我们使用一对三个单引号或者一对三个双引号来定义文档字符串,文档字符串通常第一行以大写字母开头,以句号结束,第二行是空行,从第三行开始是详细描述。
文档字符串的作用:
文档字符串是我们使用python过程中的一个重要的工具,它对文档很有帮助,使程序容易理解。甚至当程序运行的时候,我们可以从一个函数中返回字符文档。把函数当做一个对象来看的话,相当于我们获取了一个对象的属性(_doc_)
def printMax(x,y):
'''打印两个数中的最大值。
两个数必须都是整形数。'''
x=int(x)
y=int(y)
if x>y:
print(x,'最大')
else:
print(y,'最大')
print(printMax(3,5))
help(printMax) #调用函数help()时,可以将该文档字符串余函数的调用一起进行展示
结果为:5 最大
None
Help on function printMax in module __main__:
printMax(x, y)
打印两个数中的最大值。
两个数必须都是整形数。
补充:查看Python的模块和函数帮助文档方法:
Python自带的查看帮助功能,可以在编程时不中断地迅速找到所需模块和函数的使用方法。
- l通用帮助函数help(),进入help帮助文档界面,根据屏幕提示可以继续键入相应关键词进行查询,继续键入modules可以列出当前所有安装的模块。
- l查询特定的模块和函数帮助信息:
- 查询.py结尾的普通模块help(module_name),使用help(module_name)时首先需要导入(import)模块
- 查询内建模块sys.bultin_modulenames 注意需要导入sys模块
- l 查询函数信息:
查看模块下所有函数dir(module_name)
import math
print(dir(math)) #运行结果:列举出math模块下所有的函数模块
['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan', 'pi', 'pow', 'radians', 'remainder', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'tau', 'trunc']
查看模块下特定函数信息help(module_name.func_name)
import math
print(help(math.sin))
运行结果:
Help on built-in function sin in module math:
sin(x, /)
Return the sine of x (measured in radians).
None
查看函数信息的另一种方法:print(func_name._doc_)
例如:print(print.__doc__)
print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
Prints the values to a stream, or to sys.stdout by default.
Optional keyword arguments:
file: a file-like object (stream); defaults to the current sys.stdout.
sep: string inserted between values, default a space.
end: string appended after the last value, default a newline.
flush: whether to forcibly flush the stream.
(2) 函数是对象,可以将函数作为参数传递,也可更改名称或者删除它们。
(3) 偏函数应用:
(w,t):-f(w,t)=sin(2*np.pi*wt)是一个双变量函数。对于给定的参数值w,这种解释解释将两个变量中的函数简化为变量t。
部分应用程序:这种通过固定(冻结)函数的一个函数或者多个参数来定义新函数的过程称为部分应用程序。
偏函数可以使用python模块functools来轻松创建,该模块为实现这个目的提供一个名为partial函数。
6. 匿名函数--lambda关键字
Python使用lambda来创建匿名函数。所谓匿名,即不再使用def关键字以标准的形式定义一个函数。开发者可能只想对能够用简单表达式来表示的函数执行操作,而不想对函数进行命名或者通过冗长的def块来定义函数。
(1) Lambda表达式如下特点:
- Lambda只是一个表达式,函数体比def简单很多。
- Lambda的主体是一个表达式,而不是一个代码块,因而仅仅能在lambda表达式中封装有限的逻辑
- Lambda函数拥有自己的命名空间,且不能访问自有参数列表之外或全局命名空间里的参数
- 虽然lambda函数看起来只能写一行,却不等于C或C++的内联函数,后者的目的是调用小函数时不占用栈内存从而提高运行内存。
(2) Lambda函数的语法只包含一个语句,格式如下:
Lambda[arg1,[arg2,......,argn]]:expression
Lambda函数的定义只能由单个表达式组成,尤其不能包含循环。像其他函数一样,lambda函数也可以作为对象分配给变量。
注意:使用lambda函数应该注意的几点:
Lambda定义的单行函数,如需要复杂的函数,应该定义普通函数。
Lambda参数列表可以包含多个参数,如lambdax,y:x+y
Lambda中的表达式不能含有命令,而且只限一条表达式。
如:parabola=lambda x: x**2+5
print(parabola(3))
示例如下:lambda函数的使用--加法与减法
#自定义函数
sum=lambda arg1,arg2:arg1+arg2
sub=lambda arg1,arg2:arg1-arg2
#调用sum函数
print('相加的值:',sum(10,22))
print('相减的值:',sub(20,5)) 结果为: 相加的值: 32
相减的值: 15
(4) lambda函数提供了制作闭包的途径
闭包的含义:一个定义在函数内部的函数,闭包使得变量即使脱离了该函数的作用域范围也依然能被访问到(在一个外函数中定义一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用,这样就构成了一个闭包)。
7.装饰器
① 背景:如果想要看看以前写过的一个函数在数集上的运行时间是多少,这时可以修改之前的代码为它加上新的东西,来实现这样的功能。但这样做有些繁琐,那么应该采用一种可以不对源代码做任何修饰,并且能够很好的实现所有需求的手段--这里就是用Python装饰器来实现的。
② 前提是:知道闭包函数,这种函数只可以在外部函数的作用域内被正常调用,在外部函数的作用域之外调用绘报错。如果内部函数里引用了外部函数里定义的对象(甚至是外层之外,但不是全局变量),那么此时内部函数就会被称为闭包函数,闭包函数所引用的外部定义的变量被叫做*变量。闭包函数可以将其自己的代码和作用域以及外部函数的作用结合在一起。
例如:
def count():
a=1
b=2
def sum():
c=1
return(a+c) #注意:a是*变量,return sum
③ 定义:装饰器是python中的语法元素,它可以不改变函数本身定义的情况下很方便地变更函数的行为。Python装饰器本质上就是一个函数,它可以让其他函数在不需要代码变动的前提下增加额外的功能,装饰器的返回值也是一个函数对象。
装饰器函数的外部函数传入我要装饰的函数名字,返回经过修饰后函数的名字;内层函数(闭包)负责修饰被修饰函数。
④ 装饰函数属性:
实质: 是一个函数
参数:是你要装饰的函数名(并非函数调用)
返回:是装饰完的函数名(也非函数调用)
作用:为已经存在的对象添加额外的功能
特点:不需要对对象做任何的代码上的变动
⑤ 作用及应用:装饰函数最大的作用是对于已经写好的程序,我们可以抽离出一些雷同的代码组建多个特定功能的装饰器,这样就可以针对不同的需求去使用特定的装饰器。
比如应用于插入日志、性能测试、事务处理、权限校验等应用场景。
⑥ 示例:为函数添加计时功能:
示例1. 既不需要侵入,也不需要函数重复执行
import time
def deco(func):
def wrapper():
startTime = time.time()
func()
endTime = time.time()
msecs = (endTime - startTime)*1000
print("time is %d ms" %msecs)
return wrapper
@deco #相当于执行func=deco(func),为func函数装饰函数并返回
def func():
print("hello")
time.sleep(1)
print("world")
if __name__ == '__main__':
f = func #这里f被赋值为func,执行f()就是执行func()
f() #func是要装饰器的函数,想用装饰器显示func函数运行的时间
#分析:装饰器函数--decorator,该函数传入参数是被装饰函数(func),返回参数是内层函数即闭包函数(wrapper),起到装饰给定函数的作用。其中作为参数的函数func()就在返回函数wrapper()的内部执行。然后在函数func()前面加上@decorator,func()函数相当于被注入了计时功能,现在只要调用func(),其就已经变为了“新功能更多”的函数。
#注意:Python中函数返回值为func和func()的区别:
使用return func返回的func这个函数;
而使用return func()是返回func()执行后的返回值,如果func()函数没有返回值则返回值是None。
示例2: 带有不定参数的装饰器
import time
def deco(func):
def wrapper(*args, **kwargs):
startTime = time.time()
func(*args, **kwargs)
endTime = time.time()
msecs = (endTime - startTime)*1000
print("time is %d ms" %msecs)
return wrapper
@deco
def func(a,b):
print("hello,here is a func for add :")
time.sleep(1)
print("result is %d" %(a+b))
@deco
def func2(a,b,c):
print("hello,here is a func for add :")
time.sleep(1)
print("result is %d" %(a+b+c))
if __name__ == '__main__':
f = func
func2(3,4,5)
f(3,4) #func()
示例3. 带有不定参数的多个装饰器
import time
def deco01(func):
def wrapper(*args, **kwargs):
print("this is deco01")
startTime = time.time()
func(*args, **kwargs)
endTime = time.time()
msecs = (endTime - startTime)*1000
print("time is %d ms" %msecs)
print("deco01 end here")
return wrapper
def deco02(func):
def wrapper(*args, **kwargs):
print("this is deco02")
func(*args, **kwargs)
print("deco02 end here")
return wrapper
@deco01
@deco02
def func(a,b):
print("hello,here is a func for add :")
time.sleep(1)
print("result is %d" %(a+b))
if __name__ == '__main__':
f = func
f(3,4)
#func()
运行结果:
this is deco01
this is deco02
hello,here is a func for add :
result is 7
deco02 end here
time is 1001 ms
deco01 end here #注意:多个装饰器执行的顺序就是从最后一个装饰器开始,执行到第一个装饰器,再执行函数本身