Python函数详解:从基础语法到高级特性
本文深入解析Python函数的核心机制,涵盖定义语法、参数传递(位置与关键字)、可变对象陷阱、不定长参数收集与解包、变量作用域及global/globals用法。同时详解局部函数、递归原理、函数作为对象的高级应用,以及Lambda表达式的实战技巧,帮助开发者掌握Python编程的关键技能。
一、函数入门
1.概念
- 函数是一段可重复执行特定任务的代码块,拥有独立且固定的输入输出接口。
- 定义函数的本质是为代码片段命名,以便后续重复调用。
- 为了便于调用,定义时需明确指定输入(参数)与输出(返回值)。
2.定义函数的语法格式
def 函数名(形参列表):
#可执行语句
return 返回值
函数名
- 函数名需为合法标识符,遵循变量命名规则。
- 为提高可读性,建议函数名由一个或多个有意义的单词组成,单词间用下划线_分隔,且全部使用小写字母。
形参列表
- 位于函数名后的括号内,多个形参以逗号分隔,也可以没有参数。
- 参数可设置默认值,通过等号=直接指定;带有默认值的参数必须位于参数列表末尾。
- 没有默认值的参数在调用时必须显式指定。
- 即使没有形参,括号也不能省略。
- 调用带有默认值的参数时,建议指定参数名。
返回值
- 函数可以没有返回值,此时可省略return语句。
- 返回值可以是一个或多个,多个返回值以逗号分隔,组合成一个元组。
- 返回值还可以是表达式。
- 若不需要多个返回值中的某些值,可用下划线_进行占位忽略。
3.函数的文档(注释→help)
- 通过注释文字对函数进行解释说明。
- 使用help()可查看函数文档,只需在函数声明行后紧跟一段字符串,即可被help识别。
4.举例
# 函数定义
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),此时位置信息被忽略。
- 同时传递位置参数与关键字参数时,必须先传递位置参数,再传递关键字参数。
- 定义函数时可指定默认值,但带默认值的参数必须列在参数列表的最后。
#举一个小栗子,计算纸箱子的体积
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.可变对象
- 若参数为可变对象(如列表),函数内部对该对象的修改在函数执行后依然有效。
- 若默认参数是可变对象,函数内部修改该对象后,函数的默认值也会随之改变。
- 实际传递的是对象的地址,函数体不返回地址,但地址对应的值发生了变化。
# 对列表的乘方运算
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中元素的更改,当函数退出之后依然有效
利用可变对象的特点,可以制作一种隐藏的参数记录器。
# 隐藏的参数记录器
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个。
- 定义参数时,需在形参前加一个星号“*”(形参为空元组)。
- 带星号的参数可位于参数列表任意位置,但Python要求一个函数只能有一个带星号参数。
# 不定个数的数字求和
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无法区分,会将它们归入带星参数。
# 不定个数的数字乘方后求和
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.解决一个实际问题
# 不定个数的数字加权求和
# 权重随着数字的个数而发生变化
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除支持一个星号参数外,还支持两个星号的参数,用于收集关键字参数。
- 一个函数至多可带一个一星参数(收集位置参数)和一个二星参数(收集关键字参数)。
- 二星参数在函数内部以字典形式存在。
- 二星参数必须位于参数列表末尾,其后不能再有其他关键字或位置参数。
# 测试一星参数和两星参数
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)
# 报错了,二星参数后面不能再传递关键字参数了(当然位置参数也不行)
“参数收集”功能旨在让带星参数尽可能少地收集,将更多参数留给正常的位置和关键字参数。
# 如果有默认参数,要注意可能引起的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.逆向参数收集(炸开参数)
- 在函数外部定义的列表、元组、字典等,传参时可被“炸开”,内容自动分配到参数列表中。
- “炸”列表或元组,需在前面添加一个星号。
- “炸”字典,需在前面添加两个星号。
# 炸参数例子
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参数传递传递的是参数值而非地址,参数值被复制后传入函数。
- 对于数值类型参数(整型、浮点、复数等),在函数内修改参数值,函数外部不受影响。
- 对于容器类型参数(列表、字典、字符串等),在函数内修改容器内容,函数外部也会体现变化。
# 传递数值类型参数
# 在函数内修改,在函数外面不受影响
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()方法。
# 列表通过函数传参时,被改动了数据
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)的,仅在函数内部可访问,外部不可见。
- 全局变量与局部变量重名时,函数内访问局部变量,函数外访问全局变量。
- 函数内部能访问全局变量,但不能直接修改。
- 若非要在函数内部修改全局变量,需进行声明(不推荐)。
gv1 = 1
def test():
# gv1=2
print('在函数内部访问全局变量:gv1 = %d' % gv1) #1
# gv1=2
test()
print('在函数外部访问全局变量:gv1 = %d' % gv1) #1
- 上述例子中,gv1 = 2的前一行会报错,看似匪夷所思。
- 这属于Python对全局变量的“遮蔽”(hide)操作。在函数内部对不存在的变量赋值时,默认重新定义局部变量。整个函数内部gv1都被重新定义,影响整函数,因此上一行报错。
- 为访问被遮蔽的全局变量,需使用globals()函数,以字典形式输出全局变量(globals()['全局变量名']),或简单认为全局变量通过globals()中的字典存储。
- 目前得知Python 3.10以后不会报错,但这种操作一般不推荐。
# 访问被遮蔽的全局变量
gv1 = 1
def test():
# 用globals函数访问被遮蔽的全局变量
print('在函数内部访问全局变量:gv1 = %d' % globals()['gv1'])
gv1 = 2
print('在函数内部访问修改后的全局变量:gv1 = %d' % gv1)
test()
print('在函数外部访问全局变量:gv1 = %d' % gv1) # 函数内部修改的其实是同名局部变量,全局变量没有被修改。
- 正常做法是,只要定义了全局变量,函数内部的局部变量就不应与其重名。
- 可使用global语句在函数内部声明全局变量,经声明的全局变量在函数内部可访问和修改。
# 测试全局变量
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函数可作为返回值,若将局部函数作为返回值,便可在其他函数中使用。
一个栗子(利用局部函数实现多种平均值的切换)
# 利用局部函数实现多种平均值的切换
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声明全局变量)。
# 局部函数内的变量与函数内的局部变量相冲突,这个程序会报错
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
用nonlocal声明的方式可以使用/更改局部变量。
# 局部函数内的变量与函数内的局部变量相冲突,应该改成这样就不报错了
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语言中的函数指针,但更灵活且不易出错。
# 以第三章栗子中mymean函数为例
# 将函数赋值给变量f
f = mymean2('arithmetic')
# 打印出来看看
print(f)
# 测试一下
x = list(range(1,10))
m = f(x)
print(m)
# 也可以像上面的例子一样,连起来写
print(mymean2('geometric')(x))
1.函数作为函数的形参
- 有时需定义一个函数,固定其大致流程,但某些部件可替换:类似汽车换发动机、电脑换显卡。
- 这种“可替换式”程序设计,在Python中可通过将函数作为形参方便实现。
2.使用函数作为返回值
- 将函数对象(局部函数或其他定义函数)作为返回值,适合“部件替换式”程序设计中判断使用哪个部件。
- 具体实现方式参见第三章局部变量栗子中的代码。
# 以第三章栗子中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.递归
- 在函数内部调用自身,称为递归。
- 递归可视为隐式循环,无需循环语句控制即可实现代码重复执行。
- 递归在大型复杂程序中非常有用,在数值和非数值算法中都能大显身手。
- 使用递归时需注意,当函数不断调用自身时,必须保证某时刻返回值确定(不再调用自身)。
# 斐波那契数列(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表达式相当于匿名函数。
# 一行中的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表达式代替局部函数
# 用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.常见数学方法的内部函数
# 判断所有元素是否为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))
总结
本文到此结束,希望带来帮助,并欢迎关注本站更多内容!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













