函数(function )是一个通用的程序结构组件。你也许已经在其他的编程语言中见到过,有时也被称为子程序或过程。简而言之,函数主要扮演了两个角色: 1)最大化代码重用和最小化代码冗余——函数允许我们整合并通用化代码,以便这些代码能在之后多次使用。2)提供了一种将一个系统分割为定义完好的不同部分的工具,独立地实现较小的任务要比一次完成整个过程要容易得多。
Python 中的函数与 C 这样的编译语言中的函数表现很不一样:1)python提供了极为灵活的参数处理机制和使用方式。2)与函数相关的主要语句和表达式,包含了函数调用语句、两种声明函数的方式 (def和lambda) 、两种管理作用域的方式 (global 和onlocal) ,以及两种传回返回值的方式 (return 和yield)。
和其它编程语言相比,python提供了极为灵活的参数处理机制和使用方式,本文重点介绍python的参数的灵活使用方式。
形参 (parameters)是指出现在函数定义中的名称,定义了一个函数能接受何种类型的实参。而 实参(参数arguments) 则是在调用函数时实际传入的值。
【下面是相关官方文档的链接,特别提示,这些页面可中英文切换:
More on Defining Functions https://docs.python.org/3/tutorial/controlflow.html#more-on-defining-functions
Parameter https://docs.python.org/3/glossary.html#term-parameter
What is the difference between arguments and parameters?
https://docs.python.org/3/faq/programming.html#faq-argument-vs-parameter
】
默认参数就是定义函数时,形参给定一个缺省默认值。
下面给出一个求幂的例子
由于我们经常计算x2,所以,可以把第二个参数n的默认值设定为2,当我们调用power(5)时,相当于调用power(5, 2)。
#定义求幂的函数
def power(x, n=2):
s = 1
while n > 0:
n = n - 1
s = s * x
return s
#下面两句调用的结果相同
print("power(3,2)=",power(3,2))
print("power(3,2)=",power(3))
运行情况参见下图:
常见的实参和形参在顺序上和数量上必须要保持一致的参数是位置参数,也叫仅限位置(positional-only)参数,例:
#函数的定义(声明),定义一个比较字符串大小的函数
def s_max(s1,s2):
if s1>s2:
return s1
else:
return s2
s1= s_max('li','wang') #函数的调用
print(s1)
运行情况参见下图:
按位置方式使用参数时,实参和形参在顺序上和数量上必须要保持一致,否则可能造成异常,具体而言:实参和形参数量不同时将抛出异常报错;实参和形参在顺序上不一致时,若实参和形参类型不一致将抛出异常报错,若实参和形参类型一致,虽然不抛出异常报错,但会产生结果与预期不符。
关键字参数指调用函数时使用形参的名字来确定传入的参数值,即调用函数时使用 形参名=值 这样的形式,这种情形不需要在乎参数的位置顺序——这种方式,参数从左至右的关系已不再重要了,因为参数是通过变量名进行传递的,而不是通过其位置。
例子、函数定义部分与上例相同,不同之处在于函数的调用部分
#函数的定义(声明),定义一个比较字符串大小的函数
def s_max(s1,s2):
if s1>s2:
return s1
else:
return s2
s1= s_max('li','wang') #函数的调用
s2= s_max(s1='li', s2='wang') #函数的调用——关键字参数
s3= s_max(s2='wang',s1='li' ) #函数的调用——关键字参数
print(s1)
print(s2)
print(s3)
运行情况参见下图:
提示,位置参数也可以使用关键字参数传入,见下例
def test(name):
return name
print(test("wang"))
print(test(name="wang2")) #位置参数也可以使用关键字参数传入
运行情况参见下图:
函数定义中的单独出现的/和*的作用
/ 和 * 是可选的;
/用来指明他前面的函数形参必须使用位置参数;
*用来指明他后面的函数形参必须为关键字参数的形式;
/ 和 *若同时出现,/ 在 *的前面。
【特殊参数https://docs.python.org/zh-cn/3/tutorial/controlflow.html#special-parameters 】
如果想在函数调用时,在某个参数位置只能使用位置参数而不能使用关键字参数传参,那么你只需要在所需位置后面放置一个/参数,例:
#注意/参数的作用
def test(name,/):
return name
print(test("wang"))
print(test(name="wang2")) #不注释掉将报错!
运行情况参见下图:
就是说,/的含义是之前的参数都是只能是位置参数——仅限位置(positional-only)参数。
如果你希望强迫调用者使用某些参数,且必须以关键字参数的形式传参,那么你只需要在所需位置的前一个位置放置一个*参数。下面调用时参数a可以任意值, 但b、c参数一定要以关键字参数的形式传参,如funA(2, b=4, c=5),否则将会报错。
#使用单星号的示例
def funA(a,*,b,c):
return a + b + c
print(funA(2, b=4, c=5))
运行情况参见下图:
现在看一个定义一个函数时,/和*都出现的例:
#定义一个函数/和*都出现的例子
def funB(a, b, /, c, *, d, e):
return(a+b+c+d+e)
print(funB(10,20,30,d=40,e=50)) # 调用
print(funB(10,20,c=30,d=40,e=50)) # 调用
运行情况参见下图:
就是说,
/
到
*
之间的参数可以被用作位置参数(
positional argument
)和关键字参数(
keyword argument
)
星号✳还可以这样使用:在定义函数时,以*开头的参数代表收集参数,以**开头的参数代表收集关键字参数。
以*开头的参数——收集参数,也称为可变数量参数或不定长参数
在Python函数中,还可以定义可变参数。顾名思义,可变参数就是传入的参数个数是可变的,可以是1个、2个到任意个,还可以是0个。带有*的形参,可以容纳很多个参数(包括0个参数)。
#以单星号开头的参数示例
def change(*str):
for i in str:
print(i)
return
#调用
change("王某","喜欢读书")
运行情况参见下图:
再举一例
#以单星号开头的参数示例2
def myprint(*params):
print(params)
myprint("abc",123,"呵呵") #调用
运行情况参见下图:
将调用时提供的所有值,放在一个元组里。
#以单星号开头的参数不会收集关键字参数,这样调用myprint(x="abc",y=123,z="呵呵")会报错: TypeError: myprint() got an unexpected keyword argument 'x'
采用两个星号,像下面这样,就可以收集关键字参数:
#以单星号开头的参数示例A
def myprint2(**params):
print(params)
myprint2(x="abc",y=123,z="呵呵") #调用
运行情况参见下图:
得到一个字典。下面进一步介绍以**开头的参数这种情况。
以**开头的参数——收集关键字参数
带有**的形参,允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict类型数据——会将多个值转成一个字典来处理。
#以双星号开头的参数示例
def show_three(**kw):
print(type(kw))
print(kw)
#调用
show_three(name='李一',age='19',sex='女')
show_three(name='张三丰',age='20',sex='男')
运行情况参见下图:
特别说明:在Python中的代码中经常会见到*arg和**kwarg,这是Python 中的两种可变参数,是编程人员约定的变量名字,除其前面星号外你可以使用其它名称,见前面举的例子。
两者的区别,定义函数时,以*开头的参数是收集参数,以**开头的参数是收集关键字参数。
关于python之*args 和 **kwargs参数进一步说明,可参见 https://blog.csdn.net/cnds123/article/details/131223255
多种参数一起使用——组合参数,就是同时使用多种参数,定义函数时参数时的顺序有要求:普通参数(必须参数)、默认值参数、以*开头的参数、以**开头的参数。
#多种参数一起使用
def hs(a1,a2,a3=10,*a4,**cs):
print('a1=', a1, 'a2=',a2, 'a3=',a3, '*4a=',a4, '**cs=',cs)
xx={'name':'xiaozhi','age':'18','interesting':'basketball'}
hs(1,2,3,4,5,6,7,m=26,n=25,**xx) #第一次调用
tu=(1,2,3,4,5,6,7,8,'zhao')
zd={'m':26, 'n':25, 'x':'wang'}
hs(*tu, **zd) #第二次调用
运行情况参见下图:
在第一次调用函数时,1、2分别给a1,a2,形参a3=10,但是传入实参为3,改变了原来的值,因此a3=3,*a4 是不定长参数,因此4、5、6、7给*a4,以元组的形式输出,m、n以及**xx 的值给**cs,以字典形式输出。
在第二次调用函数时,tu为一个元组,用*tu调用时输出 a1=1,a2=2,a3=3,*4a=(4,5,6,7,8, 'zhao'),用**zd调用时以字典形式输出{'m': 26, 'n': 25, 'x': 'wang'}。
再给出一个组合参数示例,参考《流畅的Python》第一版5.7 中的例子:
#参考《流畅的Python》第一版5.7
#Python 最好的特性之一是提供了极为灵活的参数处理机制
def tag(name,*content,cls = None,**attrs):
""" 生成一个或多个HTML标签
:param name: 定位参数(位置参数)
:param content: 可变参数(允许传入任意(或0)个参数,通过tuple存储)
:param cls: 命名关键字参数,命名关键字参数需要一个*(或者是可变参数)作为分隔符,该分隔符后面的就是命名关键字参数,
必须传入参数名
:param attrs: 关键字参数,允许传入任意(或0)个含参数名的参数,通过dict存储
:return:
"""
if cls is not None:
attrs['class'] = cls
if attrs:
attr_str = ''.join(' %s="%s"' % (attr, value) for attr, value in sorted(attrs.items()))
else:
attr_str = ''
if content:
return '\n'.join('<%s%s>%s%s>' %(name, attr_str, c, name) for c in content)
else:
return '<%s%s />' % (name, attr_str)
#下面是调用
print(tag('br')) # 传入单个参数到定位参数name,生成一个指定名称的空标签 '
'
print(tag('p','hello')) #第一个参数后的任意个参数会被*content捕获,存入元组'hello
'
print(tag('p', 'hello', 'world')) #第一个参数后的任意个参数会被*content捕获,存入元组'hello
\nworld
'
print(tag('p', 'hello', id=33)) #tag函数签名中没有指定名称的关键字会被**attrs捕获,存入字典(这里是id)'hello
'tag('p', 'hello', 'world', cls='sidebar') #cls参数只能作为关键字参数传入,必须传入参数名,不然无法与位置参数进行区分 ' \n '
print(tag(content='testing', name="img")) #定位参数也可以使用关键字参数传入 ''
my_tag = {'name': 'img', 'title': 'Sunset Boulevard','src': 'sunset.jpg', 'cls': 'framed'}
print(tag(**my_tag)) #my_tag 前面加上 **,字典中的所有元素作为单个参数传入,同名键会绑定到对应的具名参数上,余下的则被 **attrs 捕获。
运行情况参见下图: