多数函数是带有参数的,通过给定不同的输入参数进行不同的操作,并得到不同的返回值。Python 的参数就是函数和调用者之间的通信协议。作为通信协议,最重要的是保持一致,即调用者和函数之间对参数的理解要求一致。
最简单的协议是类似于C语言的形参和实参。函数定义了形参,调用者传入实参。要求形参和实参个数相同,顺序也相同,即形参和实参是按照位置来匹配的。
例如,求两个参数的和,可以按以下方式定义:
def get_sum(a, b): # 定义了两个形参,一个是a,一个是b
return a+b
get_sum(1, 2) # 传递进去两个实参,一个是1,一个是2
如果参数个数不同,会导致异常。下面的例子演示了形参和实参不匹配的情形:
>>> def get_sum(a, b): # 定义函数 return a+b >>> get_sum(1) # 实参个数比形参少 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: get_sum() takes exactly 2 arguments (1 given) >>> get_sum(1, 2, 3) # 实参个数比形参多 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: get_sum() takes exactly 2 arguments (3 given)
如果参数的顺序不一致,可能不会抛出异常,但计算结果不是我们预期的。
例如,要求两个数的差,在函数定义时第一个参数表示被减数,第二个参数表示减数。但如果在调用时将顺序颠倒,第一个实参是减数,第二个实参是被减数,得到的结果就是错误的。
>>> def get_diff(a, b): # 定义求差的函数,第一个参数为被减数 return a-b >>> get_diff(10, 2) # 使用求差函数,但是实参顺序不对,导致结果错误 8
上面的办法有一个缺点,即函数的参数个数是确定的,但有些情况不能确定调用函数时输入的实参数量。最简单的例子就是 Python 3 中的 print() 函数,其可以接受任意多个参数。
>>> print(1) # 1个参数 1 >>> print(1, 2) # 2个参数 1 2 >>> print(1, 2, 3) # 3个参数 1 2 3
假定现在需要实现一个求和函数,但并不局限在两个数或者三个数的求和,即该函数要求能够接受任意数量的参数,然后求出所有实参的和并返回该和。
这时可以将所有的实参当作一个元组,定义函数如下:
def get_sum(*l):
这里和前面不同的是参数前面带有一个*,表示参数l是一个元组,该元组的每个元素都对应一个实参,而且顺序和实参的实际顺序一致。
>>> def get_sum(*l): # 定义求和函数,将参数当作元组 print("Type of l:", type(l)) ret = 0 for x in l: print(" x =", x) ret = ret + x print("sum =", ret) return ret # 求和函数定义结束 >>> get_sum(10, 2) # 使用求和函数,传入两个实参 Type of l: <class 'tuple'> x = 10 x = 2 sum = 12 12 >>> get_sum(10, 2, 11, 28, 100) # 使用求和函数,传入5个实参 Type of l: <class 'tuple'> x = 10 x = 2 x = 11 x = 28 x = 100 sum = 151 151
前面的例子是将所有的实参作为一个元组使用。那能否将部分实参作为元组使用呢?答案是可以的,但作为元组使用的参数一定要是最后的实参。我们可以将前面的实参当作普通参数使用,但后面的参数当作一个元组使用。
下面修改一下前面的求和函数,其第一个参数是一个系数k,后面是任意多个参数,其计算的结果是这些参数的和,然后乘以第一个参数k。用数学来表达就是:
y = k * (v1 + v2+ ...)
这时我们可将第一个实参k作为普通的参数,其他的实参作为元组。
下面是该函数的定义和使用情况:
>>> def get_sum(k, *l): # 第一个实参是普通参数,后面的是一个元组 print("k =", k) print("Type of l:", type(l)) ret = 0 for x in l: # 对元组内的数据求和 print(" x =", x) ret = ret + x ret = ret * k # 乘以系数k print("sum =", ret) return ret >>> get_sum(10, 2) # 10对应的是k,2放入到元组参数中 k = 10 Type of l: <class 'tuple'> x = 2 sum = 20 20 >>> get_sum(10, 2, 11, 28, 100) # 10对应的是k,其他放入到元组参数中 k = 10 Type of l: <class 'tuple'> x = 2 x = 11 x = 28 x = 100 sum = 1410 1410
上面在调用函数时采用的是普通的方法,但是在函数定义时对参数进行了特殊处理。这里正好相反,即在函数定义时使用普通的方法,但在函数调用时传入的是元组。
注意,该元组中元素的个数和顺序一定要和函数定义时一致;传入的字典对象前需要加上一个*。
>>> def func_demo(a, b, c): # 普通定义方法,需要3个参数 print(" a = %s" % a) print(" b = %s" % b) print(" c = %s" % c) >>> d = [1, 2, 3] # 传入的是列表 >>> func_demo(*d) # 列表有3个元素,没有问题 a = 1 # 注意参数对象前面的* b = 2 c = 3 >>> d = [1, 2] # 列表只有2个元素,有问题 >>> func_demo(*d) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: func_demo() missing 1 required positional argument: 'c' >>> d = (10, 20, 30) # 参数是元组,元素个数为3,也没有问题 >>> func_demo(*d) a = 10 b = 20 c = 30
将实参当作元组能解决参数个数不确定的问题,但还有一个问题,即参数顺序必须一致。如果不希望调用时固定参数的顺序,可以在调用时同时指定形参和实参,这些参数又称为关键字参数。
>>> def get_diff(sub_from, sub_val): ret = sub_from - sub_val print(" %d - %d = %d" % (sub_from, sub_val, ret)) return ret >>> get_diff(100, 10) # 普通调用方法,要求个数和顺序正确 100 - 10 = 90 90 >>> get_diff(sub_from=100, sub_val=10) # 不要求顺序一致 100 - 10 = 90 90 >>> get_diff(sub_val=10, sub_from=100) # 不要求顺序一致 100 - 10 = 90 90
对于同时使用形参和实参,同样也会面临参数个数的问题。如果不确定参数个数,该如何处理呢?最简单的一个例子就是命令行参数,有些参数被指定了,有些没有被指定,从而导致实参的个数不定。
为了解决该问题,可以将实参当作字典,该字典的键就是形参的名字,值就是实参。由于是字典,所以其元素个数是不定的,即有些参数可能存在也可能不存在。
假定某个函数,其最多可以有 3 个参数 key1、key2 和 key3,在调用时可以指定某一个参数,也可以指定某两个参数或者都指定,则该函数可以进行如下定义:
>>> def func_demo(**d): # 这里d匹配所有带有形参和实参的参数 print("type of d: %s" % type(d)) for k, v in d.items(): print(" %s => %s" % (k, v)) return None >>> func_demo(key1=1) # 只传入1个参数key1 type of d: <class 'dict'> key1 => 1 >>> func_demo(key2=1, key1=2) # 只传入2个参数,key2和key1 type of d: <class 'dict'> key2 => 1 key1 => 2 >>> func_demo(key2=1, key3=6, key1=2) # 传入3个参数,key2、key3和key1 type of d: <class 'dict'> key2 => 1 key3 => 6 key1 => 2
可以看到,使用字典来表示参数时,既不要求参数个数固定,也不要求参数顺序固定,这比前面的用法要灵活得多。
和前面类似,可以在函数定义时使用普通用法,但在调用时通过传入一个字典来解决。这时需要在传入参数前面加上两个*。同样,实参个数一定要等于形参个数。由于传入的是字典,所以顺序是没有关系的。
>>> def func_demo(a, b, c): # 普通函数定义方法 print(" a = %s" % a) print(" b = %s" % b) print(" c = %s" % c) >>> d = {"a": 10, "b":20, "c":30} # 传入字典 >>> func_demo(**d) # 注意字典对象前的** a = 10 b = 20 c = 30 >>> d = {"c":30, "a": 10, "b":20} # 参数顺序不重要 >>> func_demo(**d) a = 10 b = 20 c = 30 >>> d = {"a": 10, "b":20} # 但是参数的个数必须和形参一致 >>> func_demo(**d) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: func_demo() missing 1 required positional argument: 'c'
在定义函数时可以指定参数的默认值。这样在调用时即使不指定该参数的值也没有关系,因为该参数会自动等于定义时指定的默认值。
需要注意的是,带有默认值的参数一定是在最后面,不能放在没有默认值参数的前面。
>>> def get_sum(a, b=0): # 参数b有一个默认值0,如果未指定b的值,b就为0 return a+b >>> get_sum(1, 2) # 普通用法,指定了a和b的值 3 >>> get_sum(1) # 没有指定b的值,所以b等于默认值0 1
如果有默认值的参数在没有默认值参数的前面,那么在函数定义时会抛出语法错误的异常。下面演示了这种情况。
>>> def get_sum(a=0, b): # a有默认值,b没有默认值,a不能在b的前面 return a+b File "<stdin>", line 1 SyntaxError: non-default argument follows default argument