函数定义
函数是对能完成某种功能的语句块的封装。由函数名、参数列表和多语句构成的语句块组成,定义方式如下:
def func_name(argrs):
语句块
说明:
- def语句用于函数定义,def语句的作用是生成一个函数对象,并且将其赋值给函数名;
- 函数定义时的参数列表称之为形参
- 语句块中一般会包含一个return语句,返回函数调用的结果;如果没有那么函数在其末尾自动执行 return None
函数调用
ret = func_name(args)
说明:
- def语句只是定义一个函数对象,执行函数需要调用
- 函数调用的方式,func_name(args)
- 函数调用时写在括号中的参数称之为实参,分为位置参数和关键字参数,它们的书写形式分别为value、name=value。下面的示例中,print函数调用,1、2为位置参数(也可以是已定义的变量),sep = ' ' 为关键字参数
>>> print(1,2,sep=' ') 1 2
>>> a = 1, b= 2
>>> print(a, b, sep=' ')
1
2
函数返回
- 遇到return语句函数直接返回,后面的语句不再被执行
- 函数只能返回单值,这个值可以是任意的数据类型
函数参数之形参
形参中可能包含四种参数(排序分先后):位置参数、可变位置参数、keyword-only参数、可变keyword-only参数。
- 参数就是变量标识符,命名规范相同。
- 可变位置参数用‘*’加标识符表示,如*args;可变keyword-only参数用‘**’加标识符表示,如**kwargs。可变参数用于收集任意多基于位置或者关键字的参数。
- 四种参数在形参列表中都是可选的,但是它们的相对位置不变。
- 位置参数:通过位置进行匹配,把实参的值通过赋值传给对应位置的形参参数名,从左到右进行匹配。位置参数也可以通过 name=value 的形式传参,参数按照名称匹配,顺序无关。
>>> def f4(a, b): ... print('a =', a) ... print('b =', b) ... >>> c, d = 1, 2 >>> f4(c, d) #对应位置匹配,相当于实参分别为对应位置形参变量赋值,a=c, b=d
a = 1
b = 2
>>> f4(b = d, a = c)
a = 1
b = 2注意:因为参数是通过赋值传递的,所有实参和形参从某种意义上讲是在共享对象。如果传递的是可变对象,那么函数对形参的原地修改可能会影响到实参。
- keyword-only参数: 实参只能是 name = value 的形式,通过 name与形参中的变量进行匹配,实参顺序不影响匹配。
>>> def f5(*, a, b): #‘*’的意义在于,在语法上将a、b定义为keyword-only参数 ... return a, b ... >>> f5(1,2) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: f1() takes 0 positional arguments but 2 were given
#keyword-only参数不能通过位置进行匹配
>>> f5(b=1, a=2) (2, 1)>>> def f5(*args, a, b): ... print('args =', args) ... print('a =', a) ... print('b =', b) ...
>>> c = 3
>>> d = 4 >>> f5(1, 2, a=c, b=d) args = (1, 2) a = 3 b = 4
#形参中的*args参数收集任意多个未匹配的位置参数到一个tuple。>>> f2(a=1, 2, b=4) File "<stdin>", line 1 SyntaxError: positional argument follows keyword argument
#实参的位置参数必须位于关键字参数之前形式上,位置参数和keyword-only参数没有区别,为了定义keyword-only参数必须将其放到*或者*args后。
- 位置参数和keyword-only参数在定义时,可以定义一个默认参数值。在调用中如果对应参数没有实参传入,则使用默认值。
>>> def f6(p1, p2=0, *, k1=[], k2): #定义中p2 和 k1有默认值,传参的时候可以缺省,但是p1和k2必须有相应的参数传入,否则会报错 ... print('p1 =', p1) ... print('p2 =', p2) ... print('k1 =', k1) ... print('k2 =', k2) ...
>>> f6(1, k2=3) #1位置匹配到形参中的p1, k2匹配形参中的k2, p2 和 k1使用定义中规定的默认值 p1 = 1 p2 = 0 k1 = [] k2 = 3
>>> f6(p1 = 1, p2 = 2). #位置参数也可以通过name = value的形式传递,通过name进行匹配,传参顺序无关 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: f6() missing 1 required keyword-only argument: 'k2'
#缺少keyword-only参数:‘k2’>>> f6(1,p2 = 2, k2 = 3) # 1通过位置匹配传给p1, p2 和 k2分别通过name匹配传递,没有k1对应的实参传入,使用默认值 p1 = 1 p2 = 2 k1 = [] k2 = 3
>>> f6(1,2, k2 = 3, k1 = 4) # 4个形参都有对应实参传入,所以不需要使用默认值 p1 = 1 p2 = 2 k1 = 4 k2 = 3 - 位置参数中,有默认值的参数的定义必须放在非默认值参数之后,否则会报语法错误。keyword-only参数无此要求。
>>> def f7(a=1, b): ... return a,b ... File "<stdin>", line 1 SyntaxError: non-default argument follows default argument
函数参数之实参
实参(位置参数和关键字参数)和形参(位置参数、可变位置参数、keyword-only参数、可变keyword-only参数)之间的匹配顺序可以这样理解:
- 首先实参中的位置参数和形参的位置参数匹配
- 然后实参中的关键字参数和形参中的位置参数和keyword-only参数通过标识符(name)匹配
- 实参中多余的位置参数放入形参的可变位置参数,生称一个元组
- 实参中多余的关键字参数放入形参的可变keyword-only参数, 生成一个字典
- 没有实参匹配的形参使用默认值
实参中参数的解构
- 函数调用时,可以在集合类型前使用 * 或者 ** ,把集合元素中的结构解开,提取出所有的参数作为函数的实参
- 非字典类型使用 * 解构成位置参数
- 字典类型使用 ** 解构成关键字参数
- 提取出来的元素数目和类型要和形参需求匹配
默认参数
默认参数是在def语句运行时评估并保存的,而不是在函数调用时。在内部讲,python会将所有默认参数组合生成一个元组对象,作为函数的一个属性(__defaults__)保存。因为属性是附加到函数本身的,所有对该函数的调用都使用了同一个对象,所以对可变默认参数的修改要慎重。
>>> def saver(x = []):
... x.append(1)
... print(x)
...
>>> saver.__defaults__ #对象属性的查看方式,应用该对象的变量名加点加属性名
([],)
>>> saver()
[1]
>>> saver.__defaults__
([1],)
>>> saver()
[1, 1]
如果这不是你所想要的行为可以将默认参数值的表达式移到函数体内:
>>> def saver(x=None):
... x = x or [] #如果没有参数传入的话,x默认为None,or运算返回后面的空列表
... x.append(1)
... print(x)
...
>>> saver()
[1]
>>> saver()
[1]
or运算,从左到右计算表达式返回第一个遇到的真值,如果所有值都为False,那么返回最后一个值。如果传入的x参数为0或者空列表或者空集合等,那么就都会替换为空列表[]了,这可能也不符合预期,我们可以使用if语句:
>>> def saver(x=None):
... if x is None:
... x = []
... x.append(1)
... print(x)