一、函数入门
1.概念
- 函数是可以重复执行一定任务的代码片段,具有独立的固定的输入输出接口。
- 函数定义的本质,是给一段代码取个名字,方便以后重复使用
- 为了方便以后调用这个函数,在定义它的时候,就需要明确它的输入(参数)与输出(返回值)
2.定义函数的语法格式
1
2
3
|
def 函数名(形参列表): #可执行语句 return 返回值 |
函数名
- 只要是合法的标识符即可(同变量命名)
- 为了提高可读性,建议函数名由一个或多个有意义的单词组成,单词之间用下划线_分隔,字母全部小写
形参列表
- 在函数名后面的括号内,多个形参用逗号分隔,可以没有参数
- 参数可以有默认值,可以用等号=直接指定默认值,有默认值的参数必须排最后
- 没有默认值的参数,在调用的时候必须指定
- 形参也可以没有,但是括号不能省略
- 调用有默认值的参数要指定名字
返回值
- 返回值可以没有,直接省略return这句话
- 返回值可以是一个或多个,用逗号分隔,组合成一个元组
- 返回值还可以是表达式
- 多个返回值,不需要的用下划线顶替!
3.函数的文档(注释→help)
- 一段被注释的文字对函数进行解释。
- 可以用help()查看函数的文档,只要把一段字符串紧接着放在函数的声明行的后面,它就可以被help识别了。
4.举例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
# 函数定义 def myfunc(arg1, arg2, arg3 = None ): ''' This is a example for python documentation. 这是一个为python函数提供文档的例子。 arg1: 第一个参数的说明 arg2: 第二个参数的说明 arg3: 第三个参数的说明(这个参数有默认值) v1, v2, v3: 返回值的说明 ''' v1 = arg1 + arg2 v2 = arg1 * arg2 if arg3 is None : v3 = arg1 + arg2 else : v3 = arg1 + arg2 + arg3 return v1, v2, v3 # 函数调用 v1, v2, v3 = myfunc( 5 , 3 , arg3 = 4 ) print (v1, v2, v3) #8 15 12 # 使用arg3的默认值调用函数 v1, v2, v3 = myfunc( 5 , 3 ) print (v1, v2, v3) #8 15 8 # 忽略一个返回值 v1, v2, _ = myfunc( 5 , 3 ) print (v1, v2, v3) #8 15 8 # 看看返回值是元组tuple,在返回的过程中被自动解包 print ( type (myfunc( 5 , 3 ))) #<class 'tuple'> |
二、函数的参数
- 函数的参数是参数与外部可变的输入之间交互的通道。
- 函数的参数名称应该满足标识符命名规范,应该有明确的含义,可通过参数名称知道每个参数的含义。
- 在函数定义下面的注释中逐个注明函数(和返回值)的含义,以便用户即使不甚了解函数中的具体内容也能正确无误的使用它。
- 实参:实际参数,从外面传递来的实际的参数
- 形参:形式参数,在函数内部它形式上的名字
- 调用函数时,实参按照顺序位置与形参绑定,称为位置参数(Positional Argument)
- 也可以在调用时,写明实参与形参的对应关系,称作传递关键字参数(Keyword Argument),这时候位置信息被忽略了
- 同时传递位置参数与关键字参数,应该先传递位置参数,再传递关键字参数!
- 函数定义的时候,可以指定默认值,但带默认值的参数必须列在参数列表的最后
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
#举一个小栗子,计算纸箱子的体积 def cube_volume(length, width, height = 0.25 ): ''' 计算纸箱子的体积(单位:m) length: 长; width: 宽 height: 高(默认参数0.25) v: 返回值,纸箱的体积,单位m**3 ''' if length < = 0 : print ( 'length must larger than 0!' ) return 0 if width < = 0 : print ( 'width must larger than 0!' ) return 0 if height < = 0 : print ( 'height must larger than 0!' ) return 0 v = length * width * height print ( 'length = %.2f; width = %.2f; height = %.2f; cube volume = %.2f' % \ (length, width, height, v)) return v # 使用位置参数调用 v = cube_volume( 1 , 2 , 3 ) # 使用关键字参数调用 v = cube_volume(width = 1 , height = 2 , length = 3 ) # 位置参数和关键字参数混用 v = cube_volume( 1 , height = 2 , width = 3 ) # 关键字参数在位置参数之前会报错 # v = cube_volume(width = 1, 2, 3) |
1.可变对象
- 如果参数是可变对象(如列表),函数内部对此对象的修改会在函数执行后仍然有效
- 如果默认参数是可变对象,函数内部修改了此对象后,函数默认值也发生了改变!
- 实际函数传递进去的是地址,函数体不会将地址传递出来,但地址对应的值发生了变化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
# 对列表的乘方运算 def pow_list(x, p): ''' power of a list x: list p: power not return value ''' for i in range ( len (x)): x[i] * * = p #这样会输出乘方后的值,但不会改变x列表里的值 #因为在计算时将x中的值传入了新的参数进行计算 #for i in x: # i **= p # print(i) #print(x) x = [ 1 , 2 , 3 , 5 , 7 , 9 , 10 , 12 ] pow_list(x, 2 ) print (x) # 可见函数内部对列表x中元素的更改,当函数退出之后依然有效 |
利用可变对象的特点,可以制作一种隐藏的参数记录器
1
2
3
4
5
6
7
8
|
# 隐藏的参数记录器 def growing_list(x, y = []): y.append(x) print (y) # 重复执行growing_list(‘a')会发生什么结果? growing_list( 2 ) #[2] growing_list( '张三' ) #[2, '张三'] growing_list( 22333 ) #[2, '张三', 22333] |
2.参数收集(不定个数的参数)
- 参数收集,指定是可以往函数内传递不定个数的参数,例如有时候传递3个,有时候传递5个,有时候传递10个,等等。
- 传递不定个数的参数,要在定义参数时,加上一个星号“*”(形参为空的tuple)。
- 带星号的参数可以位于参数列表的任意位置(不一定是开头也不一定是结尾),python要求一个函数只能有一个带星参数。
1
2
3
4
5
6
7
8
9
10
|
# 不定个数的数字求和 def my_sum( * t): # 带星号的输入参数被当作元组处理 print (t, type (t)) sum = 0 for s in t: sum + = s return sum # 事实上该函数接受了不定个数的输入参数 my_sum( 1 , 2 , 3 , 4 , 2233 ) |
如果带星参数后面还有别的参数,则它们必须要用关键字参数的方式传递,否则python不知道它们到底是啥,都会给收集到带星参数里。
1
2
3
4
5
6
7
8
9
10
11
12
|
# 不定个数的数字乘方后求和 def pow_sum( * t, p): # 带星号的输入参数被当作元组处理 print (t, type (t)) sum = 0 for s in t: sum + = s * * p return sum # 最后一个参数p,需要指定关键字传递 pow_sum( 1 , 2 , 3 , 4 , 2233 ,p = 2 ) # 如果不指定关键字传递呢?会报错 # pow_sum(1,2,3,4,2233,2) |
3.解决一个实际问题
1
2
3
4
5
6
7
8
9
10
11
12
|
# 不定个数的数字加权求和 # 权重随着数字的个数而发生变化 def weighted_sum(x1,x2, * y): sum = 0 n = len (y) weight = 1 / 3 / n for i in y: sum + = weight * i return sum + 1 / 3 * x1 + 1 / 3 * x2 weighted_sum( 1 , 2 , 3 ) weighted_sum( 1 , 2 , 3 , 22 , 44 , 55 ) weighted_sum( 1 , 2 , 3 , 4 , 5 , 6 ) |
4.参数收集(收集关键字参数)
- python除了带一个型号的参数,还支持带两个星号的参数。它的功能是收集关键字参数。
- 一个函数,至多可以带一个一星参数(收集位置参数),加上一个二星参数(收集关键字参数)。
- 二星参数在函数内部以字典的形式存在。
- 二星参数必须在参数列表的末尾,它后面不能再有别的关键字参数和位置参数了。
1
2
3
4
5
6
7
8
9
|
# 测试一星参数和两星参数 def test_star(a, b, c, * onestar, * * twostar): print ( 'a = %d; b = %d; c = %d' % (a, b, c)) print (onestar, type (onestar)) print (twostar, type (twostar)) test_star( 1 , 2 , 3 , 4 , 5 , 6 , s1 = 7 , s2 = 8 , s3 = 9 ) # 换个顺序呢? # test_star(1, 2, 3, 4, 5, 6, s1 = 7, s2 = 8, s3 = 9, a = 10, b = 11, c = 12) # 报错了,二星参数后面不能再传递关键字参数了(当然位置参数也不行) |
“参数收集”功能,会让带星参数尽量少的收集,把更多参数留给正常的位置参数和关键字参数
1
2
3
4
5
6
7
|
# 如果有默认参数,要注意可能引起的bug def test_star(a, b, c, p = 5 , * onestar, * * twostar): print ( 'a = %d; b = %d; c = %d; p = %d' % (a, b, c, p)) #a = 1; b = 2; c = 3; p = 4 print (onestar, type (onestar)) #(5, 6) <class 'tuple'> print (twostar, type (twostar)) #{'s1': 7, 's2': 8, 's3': 9} <class 'dict'> # 会传递一个p=4进去,而不是设想的,onestar=(4,5,6) test_star( 1 , 2 , 3 , 4 , 5 , 6 , s1 = 7 , s2 = 8 , s3 = 9 ) |
5.逆向参数收集(炸开参数)
- 在参数外部定义好了的列表、元组、字典等,可以在传参的时候被“炸开”,其中的内容被自动分配到参数列表中
- “炸”列表或者元组,需要在前面添加一个星号。
- “炸”字典,需要在前面添加两个星号。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
# 炸参数例子 def zha(a,b,c): print (a,b,c) # 炸元组 z = ( 1 , 2 , 3 ) #1 2 3 zha( * z) # 炸列表 z = [ 4 , 5 , 6 ] #4 5 6 zha( * z) # 炸字典 z = { 'a' : 7 , 'b' : 8 , 'c' : 9 } #7 8 9 zha( * * z) # 炸字典 z = { 'c' : 7 , 'a' : 8 , 'b' : 9 } #8 9 7 zha( * * z) # 如果炸开后参数个数或Key不匹配,会报错 # z = {'c':7,'a':8} # zha(**z) |
6.参数的内存管理
- python的参数传递,传递的是参数值而非参数地址。参数值被复制后传递进函数。
- 对于数值类型的参数(整型、浮点、复数等),在函数内改变参数值,函数外面不受影响。
- 对于容器类型的参数(列表、字典、字符串等),在函数内改变了容器里的内容,在函数的外面也可以体现出来。
1
2
3
4
5
6
7
8
9
10
11
12
|
# 传递数值类型参数 # 在函数内修改,在函数外面不受影响 def mod_para1(a,b): print ( 'In mod_para1, before modification: a = %d; b = %d' % (a,b)) #a = 2; b = 8 a * = 2 b + = 4 print ( 'In mod_para1, after modification: a = %d; b = %d' % (a,b)) #a = 4; b = 12 a = 2 b = 8 print ( 'Out of mod_para1, before modification: a = %d; b = %d' % (a,b)) #a = 2; b = 8 mod_para1(a,b) print ( 'Out of mod_para1, after modification: a = %d; b = %d' % (a,b)) #a = 2; b = 8 |
- 传递容器类型参数
- 在函数内修改,在函数外面也能体现,也可以用这种方法向外界传递信息
- 如果不希望容器类型中的内容被修改,请手动使用copy.copy() copy.deepcopy()方法
1
2
3
4
5
6
7
8
9
10
11
12
13
|
# 列表通过函数传参时,被改动了数据 def mod_para2(x): print ( 'In mod_para2, before modification: x = ' + str (x)) for i in range ( len (x)): x[i] * = 2 print ( 'In mod_para2, after modification: x = ' + str (x)) x = [i for i in range ( 10 )] print ( 'Out of mod_para2, before modification: x = ' + str (x)) mod_para2(x) print ( 'Out of mod_para2, after modification: x = ' + str (x)) import copy A = [ 1 , 2 , 3 ]; B = copy.copy(A) mod_para2(B); print (A,B) |
7.函数中变量的作用域
- 创建于函数外部,它是全局(Global)的,它在这个py文件内部的任何地方可见。
- 创建于函数内部,它是局部(Local)的,它只能在函数内部才能访问,在函数外部不可见。
- 全局变量和局部变量重名,函数内会访问到局部变量,函数外访问到全局变量。
- 函数内部能访问全局变量,但不能修改!
- 如果非要在函数内部修改全局变量,需要声明(不推荐这么干!)
1
2
3
4
5
6
7
|
gv1 = 1 def test(): # gv1=2 print ( '在函数内部访问全局变量:gv1 = %d' % gv1) #1 # gv1=2 test() print ( '在函数外部访问全局变量:gv1 = %d' % gv1) #1 |
- 上面的例子,会在gv1 = 2的前一行,报错,看起来匪夷所思。
- 事实上,这属于python对全局变量的“遮蔽”(hide)操作。在python的函数内部对不存在的变量赋值时,默认会重新定义局部变量。也就是说,在整个函数的内部,gv1都被重新定义了,这一操作会影响整个函数,因此会在它的上一行报错。
- 为了访问被遮蔽的全局变量,需要使用globals()函数,将全局变量以字典的形式输出。(globals()['全局变量名'])——或者可以简单认为出全局变量通过globals()中的字典存储
- 目前得知python3.10以后是不会报错了,但这种操作方法我们一般是不推荐的!
1
2
3
4
5
6
7
8
9
|
# 访问被遮蔽的全局变量 gv1 = 1 def test(): # 用globals函数访问被遮蔽的全局变量 print ( '在函数内部访问全局变量:gv1 = %d' % globals ()[ 'gv1' ]) gv1 = 2 print ( '在函数内部访问修改后的全局变量:gv1 = %d' % gv1) test() print ( '在函数外部访问全局变量:gv1 = %d' % gv1) # 函数内部修改的其实是同名局部变量,全局变量没有被修改。 |
- 正常的做法是,只要有定义全局变量,函数内部的局部变量就不应该和它重名!
- 可以用global语句,在函数内部声明全局变量,经过声明的全局变量在函数内部可以访问和修改。
1
2
3
4
5
6
7
8
|
# 测试全局变量 gv1 = 1 def test(): global gv1 #全局变量我来撑控 print ( '在函数内部访问全局变量:gv1 = %d' % gv1) #1 gv1 + = 1 test() print ( '在函数外部访问全局变量:gv1 = %d' % gv1) #2 |
8.获取指定范围内的变量
- python提供了多个方法可以让我们访问到每个变量的“名字”和他们持有的“值”
- 变量在内存的某处保存着“名字”-“值”对儿
- globals(): 返回全局范围内所有变量组成的字典, globals()[“名字”]
- locals(): 返回当前函数范围内的所有变量组成的字典
- vars(object): 获取指定对象范围内的所有变量组成的字典(如果不传入object参数,vars和locals的作用完全相同)
- 如果在全局范围内(在函数外部)调用locals(),则它的行为和globals()一样,也会列出全局范围内所有变量
- 一般来说,上述函数所列出的变量字典,都不应该被修改!但事实上它们可以被修改!!不推荐使用这种方式修改变量。
三、局部函数(函数的嵌套)
- python可以在函数的内部定义函数,多个函数相互嵌套。在其它函数内部的函数称为“局部函数”。
- 局部函数是对外隐藏的,只能封闭在定义它的那一个函数的内部使用。
- python的函数也可以作为返回值,如果把局部函数作为返回值,就可以在其它函数中使用了。
一个栗子(利用局部函数实现多种平均值的切换)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
# 利用局部函数实现多种平均值的切换 def mymean(x, mtype = 'arithmetic' ): '''计算列表x的平均值,用mtype定义计算哪种平均值,默认为算术平均值(arithmetic mean) ''' def arithmetic(x): ''' 算术平均值(arithmetic mean) ''' m = sum (x) / len (x); return m def geometric(x): '''几何平均值(geometric mean) ''' p = 1. ; n = len (x) for i in range (n): p * = x[i] m = p * * ( 1 / n); return m def harmonic(x): ''' 调和平均值(harmonic mean) ''' s = 0. ; n = len (x) for i in range (n): s + = 1 / x[i] m = 1 / (s / n); return m if mtype = = 'arithmetic' : return arithmetic elif mtype = = 'geometric' : return geometric elif mtype = = 'harmonic' : return harmonic else : return arithmetic |
- 类似于函数内局部变量遮蔽全局变量,局部函数内的变量也会遮蔽它所在函数的局部变量。
- 因此使用局部函数时,同样要注意变量名的问题,不同层次的函数变量名应该不同。
- 如果要访问上一层函数的局部变量,在局部函数中应该用nonlocal声明(类比于用global声明全局变量)。
1
2
3
4
5
6
7
8
9
10
11
|
# 局部函数内的变量与函数内的局部变量相冲突,这个程序会报错 def test1(): fv = 1 def test2(): # print('局部函数内打印上层函数中的局部变量:%d' % fv) # 会在这里报错 fv = 2 print ( '局部函数内打印上层函数中的局部变量(更改后):%d' % fv) #2 test2() print ( '上层函数内打印局部变量(更改后):%d' % fv) #1 return fv print ( '上层函数外打印局部变量(更改后):%d' % test1()) #1 |
用nolocal声明的方式可以使用/更改全局变量
1
2
3
4
5
6
7
8
9
10
11
12
|
# 局部函数内的变量与函数内的局部变量相冲突,应该改成这样就不报错了 def test1(): fv = 1 def test2(): nonlocal fv # 用nonlocal声明,把fv声明为上一层函数的变量 print ( '局部函数内打印上层函数中的局部变量:%d' % fv) #1 fv = 2 print ( '局部函数内打印上层函数中的局部变量(更改后):%d' % fv) #2 test2() print ( '上层函数内打印局部变量(更改后):%d' % fv) #2 return fv print ( '上层函数外打印局部变量(更改后):%d' % test1()) #2 |
四、函数的高级内容
- python中万物皆对象,函数也是对象。函数可以赋值给变量,可以作为函数的参数,也可以作为函数的返回值。
- python中以函数作为对象的用法,可以类比于c语言中的函数指针,但比函数指针灵活的多,也更不容易出错。
1
2
3
4
5
6
7
8
9
10
11
|
# 以第三章栗子中mymean函数为例 # 将函数赋值给变量f f = mymean2( 'arithmetic' ) # 打印出来看看 print (f) # 测试一下 x = list ( range ( 1 , 10 )) m = f(x) print (m) # 也可以像上面的例子一样,连起来写 print (mymean2( 'geometric' )(x)) |
1.函数作为函数的形参
- 有时候需要定义一个函数,让它内部的大致流程都固定下来,但其中某些部件可以替换:类似于汽车换发动机,电脑换显卡。
- 这种“可替换式”的程序设计方式,在python中可以方便的通过将函数作为形参的方式来实现。
2.使用函数作为返回值
- 将一个函数对象(可以是局部函数,也可以是别的地方定义的函数)作为返回值,适合“部件替换式”程序设计中,判断使用哪个部件。
- 具体实现方式参见第三章局部变量栗子中的代码
1
2
3
4
5
6
7
8
9
10
11
|
# 以第三章栗子中mymean函数为例 # 编写另一个程序,对列表中的数字进行变换,变成均值为1的另一个列表 # 均值,可以是算术平均值、几何平均值、调和平均值 def mynormalize(x, mtype): f = mymean(mtype) m = f(x) return [i / m for i in x] x = list ( range ( 1 , 10 )) mtype = 'geometric' print (mymean(mtype)(x)) print (mynormalize(x, mtype)) |
3.递归
- 在一个函数里面调用它自己,称为递归。
- 递归可以视作一种隐式的循环,不需要循环语句控制也可实现重复执行某段代码。
- 递归在大型复杂程序中非常有用,在数值和非数值算法中都能大显身手!
- 使用递归的时候要注意,当一个函数不断调用自己的时候,必须保证在某个时刻函数的返回值是确定的,即不再调用自己。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
# 斐波那契数列(Fibonacci sequence) # 在现代物理、准晶体结构、化学等领域,斐波纳契数列都有直接的应用 def Fibonacci(n): ''' Fibonacci sequence f(0)=1, f(1) = 1, f(n) = f(n-1)+f(n-2) ''' if n = = 0 or n = = 1 : return 1 else : return Fibonacci(n - 1 ) + Fibonacci(n - 2 ) # 测试一下,注意n不要设的太大,python的递归效率是比较低的,太大会死机 print (Fibonacci( 5 )) # 斐波那契数列,前20位 print ( 'Fibonacci sequence:' ) for i in range ( 20 ): print ( '%d: %d' % (i,Fibonacci(i))) |
五、局部函数与lambda
- lambda表达式是现代编程语言引入的一种函数实现方式,它可以在一定程度上代替局部函数。
- 对于局部函数,它的名字只在函数内部有意义,在函数外部看不到它的名字。即便使用返回值的形式传出来了,它的名字并没有被同时传出来。
- 从命名的意义上讲,局部函数都是“隐姓埋名”的,出了这个函数就没人知道它的名字。
- lambda表达式就相当于匿名函数。
1
2
3
4
5
6
7
|
# 一行中的hello world greeting = lambda : print ( 'Hello lambda!' ) greeting() # lambda表达式可以放在数组里面,批量运行 L = [ lambda x: x * * 2 , lambda x: x * * 3 , lambda x: x * * 4 ] for p in L: print (p( 3 )) |
1.用lambda表达式代替局部函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
# 用lambda表达式代替局部函数 def mymean2(mtype = 'arithmetic' ): ''' 返回计算平均值所用的函数,用mtype定义计算哪种平均值,默认为算术平均值(arithmetic mean) ''' # 由于lambda表达式只能写一行,这里用numpy和scipy的现成的函数来实现 import numpy as np import scipy.stats as st a = np.array(x) if mtype = = 'arithmetic' : # 算术平均值(arithmetic mean) return lambda a: np.mean(a) elif mtype = = 'geometric' : # 几何平均值(geometric mean) return lambda a: st.gmean(a) elif mtype = = 'harmonic' : # 调和平均值(harmonic mean) return lambda a: st.hmean(a) else : # 默认:算术平均值(arithmetic mean) return lambda a: np.mean(a) x = list ( range ( 1 , 10 )) print (x) print (mymean2( 'arithmetic' )(x)) print (mymean2( 'geometric' )(x)) print (mymean2( 'harmonic' )(x)) |
2.常见数学方法的内部函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
# 判断所有元素是否为True,相当于多重的and help ( all ) print ( all ([ 3 > 2 , 6 < 9 ])) # 任意一个元素是否为True,相当于多重的or help ( any ) print ( any ([ 3 > 2 , 6 < 9 ])) # 最大值和最小值 help ( max ) help ( min ) print ( max ([ 1 , 2 , 5 , 3 ])) print ( min ([ 1 , 2 , 5 , 3 ])) # 四舍五入(到小数点后第n位) help ( round ) print ( round ( 3.1415926 , 3 )) # 所有元素相加 help ( sum ) print ( sum ([ 1 , 2 , 3 ])) print ( sum ([ 1 , 2 , 3 ], 5 )) # 乘幂 help ( pow ) print ( pow ( 6 , 2 )) print ( pow ( 6 , 2 , 5 )) # 带余除法 help ( divmod ) print ( divmod ( 6 , 2 )) # 绝对值 help ( abs ) print ( abs ( - 2.56 )) |
总结
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注服务器之家的更多内容!
原文链接:https://blog.csdn.net/qq_52057693/article/details/120954218