# 函数的参数 定义函数的时候,我们把参数的名字和位置确定下来,函数的接口定义就算完成了。 对于函数的调用者来说,只需要知道如何传递正确的参数,以及函数将返回什么样的值就够了 函数内政部的复杂逻辑被封装起来,调用者无需了解。 python的函数定义非常简单,单灵活度却非常大。除了正常定义的必选参数外, 还可以使用默认参数、可变参数和关键字参数,使得函数定义出来的接口,不但能处理复杂的参数 还可以简化调用者的代码。 一、位置参数 我们先写一个计算x的平方的函数 def power(x): return x * x 对于power(x)函数,参数想就是一个位置参数。当我们调用power函数时,必须传入有且仅有的一个参数x >>> power(5) 25 >>> power(15) 225 >>> power(125) 15625 那么,如果我们现在要计算x的立方怎么办?可以再定义一个power3函数,但是如果要计算x的四次方,五次方怎么办? 假设x的n次方,可以把x和n都设为参数 def powerful(x, n): result = 1 if n == 0: return result elif n > 0: while n >= 1: result = result * x n-=1 return result elif n < 0: while n >= 1: result = result * x result = 1 / result return result 对于这个修改后的powerful(x, n)函数,可以计算任意N次方 修改后的powerful(x, n)函数有两个参数,x和n,这两个参数都是位置参数,调用函数时,传入的两个值 按照位置顺序一次赋给参数x和n 二、默认参数 如果我们经常计算x的平方,那么我们完全可以把第二个参数的默认值设定为2 def powerful(x, n = 2): result = 1 if n == 0: return result elif n > 0: while n >= 1: result = result * x n-=1 return result elif n < 0: while n >= 1: result = result * x result = 1 / result return result >>> powerful(5) 25 >>> powerful(5,3) 125 这样如果我们给n赋值,n就会成为被赋的值,如果不给n赋值,默认就是n=2,默认计算平方。 从以上的例子可以看出,默认参数可以简化函数的调用。 设置默认参数时,有几点问题需要注意: 一是必选参数在前,默认参数在后,否则python的解释器会报错。 二是如何设置默认参数。 当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面。 变化小的参数就可以作为默认参数。使用默认参数最大的好处就是能够降低使用函数的难度。 举个例子,我们写一个小学一年级学生注册的函数,需要传入name和gender两个参数 def enroll(name, gender): print("name:", name) print("gender:", gender) 这样,调用enroll()函数只需要传入两个参数 >>> enroll("Sarah", "F") name: Sarah gender: F 如果要继续传入年龄,城市信息怎么办? 我们如果规定一大堆位置参数,会使得调用函数的难度大大增加 因为位置参数需要严格按照位置传入,python默认从左到右给参数赋值,而位置参数一旦增多, 必然会要求函数调用者必须严格按照一定的顺序来,这样会导致出错的概率大大增加。 在本例中,由于年龄、城市信息绝大多数学生是一致的,因此可以设定为默认值 这样,大多数学生注册时不需要提供年龄和城市,只需提供必须的两个参数 只有与默认参数不符的学生才需要提供额外的信息 def enroll(name, gender, age = 6, city = "BeiJing"): print("name:",name) print("gender:",gender) print("age:",age) print("city:",city) >>> enroll("xiaowang", "M") name: xiaowang gender: M age: 6 city: BeiJing >>> enroll("xiaoshitou", "F", 8, "shanghai") name: xiaoshitou gender: F age: 8 city: shanghai >>> enroll("xiaojuzi","F", 12) name: xiaojuzi gender: F age: 12 city: BeiJing >>> enroll("ligoudan","M", city="LA") name: ligoudan gender: M age: 6 city: LA 可见,默认参数降低了函数调用的难度,而一旦需要更复杂的调用时,又可以传递更多的参数来实现 无论是简单调用还是复杂调用,函数只需要定义一个。 有多个默认参数时,调用的时候,既可以按顺序提供默认参数 比如enroll("Bob","M",7) 意思是, 除了name和gender这两个参数外,最后一个参数应用在age上, city参数由于没有提供,仍然使用默认值。 也可以不按顺序提供部分默认参数。当不按顺序提供部分默认参数时,需要把参数名写上,相当于指名道姓的给某某赋值 比如调用enroll("Adam", "M", city= "tianjin") 意思是, city参数用穿进去的值,其他默认参数继续使用默认值。 注意:默认参数很有用,但使用不当也会掉坑里。默认参数有个最大的坑,演示如下: 先定义一个函数,传入一个list,添加一个END再返回。 def add_end(L=[]): L.append("END") return L 当你正常调用时,结果似乎不错 >>> add_end([1,2,3]) [1, 2, 3, 'END'] >>> add_end(["anything you want","shuzu","data",1985]) ['anything you want', 'shuzu', 'data', 1985, 'END'] 当时使用默认参数时,一开始的结果也是对的: >>> add_end() ['END'] 但是,当你再次调用时,结果就不对了 >>> add_end() ['END', 'END'] >>> add_end() ['END', 'END', 'END'] 很多人开始疑惑,默认参数是一个空列表[], 但是函数似乎每次都记住了上次添加了“END”后的list 原因解释如下: python函数在定义的时候,默认参数L的值就被计算出来了,即[] 因为默认参数 L 也是一个变量,他指向对象[], 每次调用该函数,如果改变了 L 的内容, 则下次调用时, 默认参数的内容就变了,不再是函数定义时的[]了。 也就是说,如果你的默认参数是一个变量,而这个变量在函数执行的过程中会发生改变,那么 当你下一次重新调用这个函数意图使用函数定义时的默认参数时,就不可能了,因为默认参数已经变了 所以说,默认参数应该是一个固定的值,是大多数情况下不需要修改的值,必须指向不变对象! 要修改上面的例子,可以用None这个不变对象来实现: def add_end(L = None): if L == None: L = [] L.append("END") return L else: L.append("END") return L 这样,无论调用多少次,都不会有问题,这样传入的参数会先进行判断,如果为空,则创建一个空列表,添加一个END返回 >>> add_end([1,2,3]) [1, 2, 3, 'END'] >>> add_end() ['END'] >>> add_end() ['END'] >>> add_end() ['END'] 为什么要设计str、None这样的不可变对象呢? 因为不可变对象一旦创建,对象内部的数据就不能修改, 这样就减少了由于修改数据导致的错误。此外,由于对象不变,多任务环境下同时读取不需要加锁。 同时读取一点问题都没有。 我们在编写程序时,如果可以设计一个不可变对象,那就尽量设计成不变对象。 什么是不可变对象? 比如你创建一个字符串"李狗蛋",李狗蛋就是李狗蛋,不能变成其他的,至于你说,可以拆分成"李狗"或"李蛋" 这不是发生了对字符串的更改,而是重新生成了两个字符串"李狗"或"李蛋",他们与原来字符串"李狗蛋"的关系 仅仅是在原来字符串基础上衍生的新字符串,并没有对原字符串发生更改。 三、可变参数 在python函数中,还可以定义可变参数。 顾名思义,可变参数就是传入的参数个数是可变的,可以是1个,2个,任意个。也可以0个。 我们以数学题为例子: 给定一组数字a, b, c....,请计算a的平方 + b的平方 + c的平方 要定义出这个函数,我们必须确定输入的参数。然而,很悲伤,我们竟不知道有多少参数, 即使知道有多少个,但是总不能在函数定义时全部写出来吧! 我们可以显而易见的想到一个比较好的方法:把参数用一个列表或者元组传进来,然后遍历,再做运算,听起来不错。 def sum(numbers): sum = 0 for n in numbers: sum = sum + n return sum 但是调用的时候,需要先组装出一个list或者tuple >>> sum([1,2,3,4,5]) 15 还有更好的方法:函数定义时,设置可变参数。同 样可以遍历传入的*numbers def sum(*numbers): sum = 0 for n in numbers: sum += n return sum >>> sum(1,3,5,7) 16 >>> sum() 0 定义可变参数和定义一个list或者tuple参数相比,仅仅在参数前面加了一个 * 号。 在函数内部, 参数number接受到的是一个tuple,因此,函数的代码完全不变。 但是调用该函数时,额可以传入任意个参数,包括0个参数。 着重理解一下:*numbers 在函数内部是一个元组形式,具备元组的一些功能。 如果已经有一个现成的list或者tuple,要调用一个可变参数怎么办? 可以这样做: >>> nums= [1, 2, 3] >>> sum(nums[0], nums[1], nums[2]) 6 这种写法是可行的,但是相当繁琐。 真正好的方法是在list或者tuple前面加一个 * ,好像一个反运算,又把list或者tuple的元素拆解出来传进去了 >>> sum(*nums) 6 *nums表示把nums这个list的所有元素作为可变参数穿进去。这种写法相当有用,而且很常见。 四、关键字参数 可变参数允许你传入0个或者任意个参数,这些可变参数在函数调用时自动组装成为一个tuple。 但是,你传入的这些不确定个数的参数仅仅是一个值,至于这个值是干什么,什么属性,一切都无从得知 比如传入可变参数[1,3,5,7] 这是可以的,但是你想传入一个x,并且准备让x=9 也就是说你想传成这样的形式[1,3,5,7,x = 9] 这样该怎么办? 使用关键字参数。 关键字参数允许你传入0个或者任意个含参数名的参数,这些参数可以说有名有姓,有键有值 这些关键字参数在函数内部自动组装为一个dict 还是举小学生注册的例子: def person(name, age, **kwargs): print("name:",name, "age:",age, "other:",kwargs) 函数person出了必选参数name和age外, 还接受关键字参数kwargs 在调用该函数时,可以只传入必选参数 >>> person("stephen curry", 29) name: stephen curry age: 29 other: {} 当然咯,作为一个信息录入的函数,我们也十分喜欢用户传的信息详细些,比如这样 >>> person("stephen curry", 29, gender = "M", tags="the famous basketball star") name: stephen curry age: 29 other: {'gender': 'M', 'tags': 'the famous basketball star'} 所以, 关键字参数有什么用呢? 它可以扩展函数的功能。 比如,在person函数里,我们保证能接受到name和age这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。 试想,你正在做一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项,利用关键字参数来定义这个函数 就能满足注册的需求。 当然,也可以先组装一个dict,然后用 ** 分解开传进去 >>> extra = {"reward":"twice mvp of NBA", "hobby":"golf"} >>> person("stephen curry", 29 **extra) name: stephen curry age: 29 other: {'reward': 'twice mvp of NBA', 'hobby': 'golf'} ** extra表示把extra这个dict的所有key-value用关键字参数传入到函数的 **kwargs kwargs将获得一个dict 注意kwargs获得的dict是extra的一份拷贝,对kwargs的改动不会影响函数外的extra。 五、命名关键字参数 对于关键字参数,函数的调用者可以传入任意不收限制的关键字参数,至于到底传入了哪些 就需要在函数内部通过kwargs检查 仍以person为例,我们希望检查时候有city和job参数 def person(name, gender, **kwargs): if "city" in kwargs: print("有city参数传入") if "job" in kwargs: print("有job参数传入") print("name:",name, "gender:",gender, "other:",kwargs) 但是调用者仍然可以传入不受限制的关键字参数 >>> person("stephen curry", "M", hobby="basketball", city="Golden States") 有city参数传入 name: stephen curry gender: M other: {'hobby': 'basketball', 'city': 'Golden States'} 如果要限制关键字参数的名字,就可以用命名关键字参数, 例如,只接收city和job作为关键字参数。 这种方式定义的函数如下: def person(name, age, *, city, job): print(name, age, city, job) 命名关键字参数必须传入参数名,这和位置参数不同。 >>> person("stephen curry",29, city="Colden States",job="basketball") stephen curry 29 Colden States basketball 如果没有传入参数名,调用将报错 >>> person("stephen curry",29,"GS","basketball") Traceback (most recent call last): File "<pyshell#85>", line 1, in <module> person("stephen curry",29,"GS","basketball") TypeError: person() takes 2 positional arguments but 4 were given 由于调用时缺少参数名city和job,python解释器把这4个参数均视为位置参数, 但是person()函数仅接受了2个位置参数。 正所谓不传不行,不指名也不行。 >>> person("xiaowang",24) Traceback (most recent call last): File "<pyshell#92>", line 1, in <module> person("xiaowang",24) TypeError: person() missing 2 required keyword-only arguments: 'city' and 'job' >>> person("xiaowang",24, "beijing","program") Traceback (most recent call last): File "<pyshell#93>", line 1, in <module> person("xiaowang",24, "beijing","program") TypeError: person() missing 2 required keyword-only arguments: 'city' and 'job' 命名关键字参数可以有缺省值,从而简化调用: def person(name, age, *, city="BeiJing", job): print(name, age, city, job) 由于命名关键字参数city具有默认值,调用时,可不传入city参数。 >>> person("Jack",24,job="Engineer") Jack 24 BeiJing Engineer 使用命名关键字参数时,要特别注意,如果没有可变参数,就必须加一个 * ,如果缺少 * , python解释器将无法是被位置参数和命名关键字参数 def person(name, age, city, job): # 缺少 * city和job被视为位置参数 pass 命名关键字参数表示,只接收命名的关键字,多余的不再接受 六、参数组合 在python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。 但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。 比如定义一个函数,包含上述若干种参数: def f1(a, b, c=0, *args, **kwargs): print("a = ", a, "b = ", b, "c = ", c, "args = ", args, "kwargs = ", kwargs) def f2(a, b, c=0, *, d, **kwargs): print("a = ", a, "b = ", b, "c = ", c, "d = ", d, "kwargs = ", kwargs) 在函数调用的时候,python解释器会自动按照参数位置和参数名把对应的参数传进去。 >>> f1(1,2) a = 1 b = 2 c = 0 args = () kwargs = {} >>> f1(1,2,c=3) a = 1 b = 2 c = 3 args = () kwargs = {} >>> f1(1,2,3) a = 1 b = 2 c = 3 args = () kwargs = {} >>> f1(1,2,3,4) a = 1 b = 2 c = 3 args = (4,) kwargs = {} >>> f1(1,2,3,4,5) a = 1 b = 2 c = 3 args = (4, 5) kwargs = {} >>> f1(1,2,3,4,5,6,7) a = 1 b = 2 c = 3 args = (4, 5, 6, 7) kwargs = {} >>> f1(1,2,3,4,5,6,x=10) a = 1 b = 2 c = 3 args = (4, 5, 6) kwargs = {'x': 10} >>> f2(1,2,3, d= 108, extra=None) a = 1 b = 2 c = 3 d = 108 kwargs = {'extra': None} 最神奇的是通过一个tuple和dict,你也可以调用上述函数: >>> f1(*args, **kwargs) a = 1 b = 2 c = 3 args = (4,) kwargs = {'d': 99, 'x': None} >>> f2(*args,**kwargs) a = 1 b = 2 c = 3 d = 99 kwargs = {'tags': 'basketball'} 所以,对于任意函数都可以通过类似func(*args, **kwargs)的形式调用它,因为加入星号*作为参数传入进函数时 就相当于把参数拆解成一个个,如果传入的参数正确,函数会自动进行一一赋值。 总结: python的函数具有非常灵活的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数。 注意:默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误。 注意:定义可变参数和关键字参数的语法 *args是可变参数, args接收的是一个tuple **kwargs 是关键字参数,kwargs接收的是一个dict 注意:调用函数时如何传入可变参数和关键字参数的语法 可变参数既可以直接传入:func(1,2,3),又可以先组装成list或者tuple通过*args传入:func(*(1,2,3)) 关键字参数既可以直接传入:func(a = 1, b = 2),又可以先组装dict,再通过**kwargs传入:func(**{"a":1,"b":2}) 使用*args 和 **kwargs 是python的习惯写法,当然也可用其他的写法,不过最好用习惯写法。 命名的关键字参数是为了限制调用者可以传入的参数,同时可以提供默认值。 注意:定义命名关键字参数在没有可变参数的情况下不要忘了写分隔符 * ,否则定义的将是位置参数。