查看原文
其他

给妹子讲python-S01E21函数参数的匹配与解包

给妹子讲python Python爱好者社区 2019-04-07

作者:酱油哥/ 清华程序猿      
微信公众号: python数据科学家
知乎专栏: 《给妹子讲python》
https://zhuanlan.zhihu.com/c_147297848



前文传送门:

给妹子讲python-S01E01好用的列表

给妹子讲python-S01E02学会用字典

给妹子讲python-S01E03元组的使用

给妹子讲python-S01E04容器遍历和列表解析式

给妹子讲python-S01E05字符串的基本用法

给妹子讲python-S01E06字符串用法进阶

给妹子讲python-S01E07字符编码历史观:从ASCII到Unicode

给妹子讲python-S01E08理清python中的字符编码方法

给妹子讲python-S01E09文件操作小意思

给妹子讲python-S01E10动态类型与共享引用

给妹子讲python-S01E11赋值与对象拷贝

给妹子讲python-S01E12循环迭代初体验

给妹子讲python-S01E13循环迭代高级技巧

给妹子讲python-S01E14可迭代对象和迭代器

给妹子讲python-S01E15迭代环境

给妹子讲python-S01E16生成器的使用

给妹子讲python-S01E17函数的基本特征

给妹子讲python-S01E18初探函数作用域

给妹子讲python-S01E19内嵌作用域与函数闭包

给妹子讲python-S01E20函数参数的传递与修改


【要点抢先看】

1.基于位置和关键字的参数匹配
2.使用默认参数形式
3.函数定义使用 * 和 ** 进行任意数目参数收集
4.函数调用时使用 * 和 ** 进行参数解包

正如我们之前所讲的,参数在python中总是通过赋值进行传递的。在默认情况下,参数是通过其位置进行匹配的,从左到右,而且必须精确的传递和函数头部参数名一样多的参数。

这种默认的传递方式很简单

def f(a,b,c):
    print(a,b,c)

f(1,2,3)

1 2 3

python中可以使用基于关键字的参数匹配形式。在调用函数的时候,能够更详尽的定义内容传递的位置。关键字参数允许通过变量名进行匹配,而不是通过位置。

def f(a,b,c):
    print(a,b,c)

f(c=3,a=1,b=2)

1 2 3

我们可以看出,当关键字参数使用时参数从左至右的关系已不再重要了,因为参数是通过变量名进行传递的,而不是通过其位置。这种调用显得更文档化一些(例如使用一些名称更直观的参数名)。

甚至在一个调用中混合使用基于位置的参数和基于关键字的参数也可以。在这种情况下,所有基于位置的参数首先按照从左到右的顺序匹配头部的参数,之后再进行基于变量名进行关键字的匹配。

def f(a,b,c):
    print(a,b,c)

f(1,c=3,b=2)

1 2 3

再来说说python中的默认参数形式。默认参数允许创建函数可选的参数,如果没有传入值的话,在函数运行前,参数就被赋予了默认值,还是看一个例子:

def f(a,b=2,c=3):
    print(a,b,c)

f(1)
1 2 3

f(a=1)
1 2 3

看到这个例子中,我们必须为a提供值,无论是通过位置参数还是关键字参数来实现,然而,为b和c提供值是可选的。如果我们不给b和c传递值,它们会分别赋值为2和3。

那么按位置顺序,当给函数传递两个值的时候,只有c得到默认值,并且当有三个值传递时,不会使用默认值。

def f(a,b=2,c=3):
    print(a,b,c)

f(1,4)

1 4 3

def f(a,b=2,c=3):
    print(a,b,c)

f(1,4,8)

1 4 8

最后一种情况,是关键字和默认参数一起使用的情况。关键字参数允许我们跳过有默认值的参数,但是要记住的是,首先要完成通过位置进行匹配的参数。

def f(a,b=2,c=3):
    print(a,b,c)

f(1,c=6)

1 2 6

很明显,a通过位置得到了1,c通过关键字得到了6,而跳过了b,b通过默认参数得到了2.

【妹子说】我有时看到函数定义的参数中有 * 和 ** 符号,表示什么意思?

这也是python中非常有特色的:当 * 和 ** 符号出现在函数定义的参数中时,表示任意数目参数收集。

先说说 * ,他是用元组的形式收集不匹配的位置参数。当这个函数调用时,python将所有位置相关的参数收集到一个新的元组中,并将这个元组赋值给变量args。

def f(a,*args):
    print(args)

f(1,2,3,4)

(2, 3, 4)

再说说 ** 的特性,他只对关键字参数有效。在这种情况下, ** 允许将关键字参数转化为字典,你能够在之后使用键调用来进行步进或字典迭代

def f(**kargs):
    print(kargs)

f(a=1,b=2)

{'b': 2, 'a': 1}

最后我们来概况一下最一般的形式。即在函数头部能混合一般参数、 * 参数以及 ** 去实现更加灵活的调用方式。

def f(a, *pargs, **kargs):
    print(a,pargs,kargs)

f(1,2,3,x=4,y=5)

1 (2, 3) {'x': 4, 'y': 5}

这个例子中,1按位置传给a,2和3收集到pargs位置的元组中,x和y放入kargs关键字词典中

上面是在函数定义的时候写的 * 和 ** 形式,那反过来,如果 * 和 ** 语法出现在函数调用中又会如何呢?他会解包参数的集合。

例如,我们在调用函数时能够使用 * 语法,在这种情况下,它与函数定义的意思相反,他会解包参数的集合,而不是创建参数的集合。例如我们可以通过一个元组给一个函数传递四个参数,并且让python将它们解包成不同的参数。

def func(a,b,c,d):
    print(a,b,c,d)

args = (1,2,3,4)
func(*args)

1 2 3 4

相似的,在函数调用时,**会以键/值对的形式解包一个字典,使其成为独立的关键字参数。

def func(a,b,c,d):
    print(a,b,c,d)

kargs = {'a':1, 'b':2, 'c':3, 'd':4}
func(**kargs)

1 2 3 4

当然这种形式也是可以混合的,从左到右要以位置参数、元组解包、关键字参数、字典解包的顺序来进行

def func(a,b,c,d,e,f):
    print(a,b,c,d,e,f)

args = (2, 3)
kargs = {'d': 4, 'e': 5}

func(1, *args, f=6, **kargs)

1 2 3 4 5 6

说了这些基本的知识,我们来看看一个实际应用的例子

这种支持任意参数形式的方法的一个应用点,就是实现对其他函数进行灵活调用。因为参数列表可以通过元组、字典形式传入,所以他支持运行时构建参数列表,这对于测试和计时其他函数非常方便。在下面的代码中,我们通过传递任何发送进来的参数来支持具有任意参数的任意函数:

def tracer(func, *args, **kargs):
    print('calling:', func.__name__)
    return func(*args, **kargs)

def func(a,b,c,d):
    return a+b+c+d

print(tracer(func,1,2,c=3,d=4))

calling: func
10

很明显,这里在定义tracer函数时应用了收集任意参数的方法,在其中调用func函数时又利用了解包参数的方法。

最后综合所学,我们举一个例子,来仿写一个函数模拟print的功能,他可以接收任意的位置参数,同时接收规定范围内的关键字参数,对多余的关键字参数会报错。

import sys
def print30(*args, **kargs):
    sep = kargs.pop('sep', ' ')
    end = kargs.pop('end', '\n')
    file = kargs.pop('file', sys.stdout)
    if kargs:
        raise TypeError('extra keywords:{}'.format(kargs))
    output = ''
    first = True
    for arg in args:
        output += ('' if first else sep) + str(arg)
        first = False
        file.write(output + end)

print30('hello','world','healthy',sep='&')

hello&world&healthy

在这段程序中,有几个关键点值得我们注意

对kargs字典进行pop操作,弹出了指定的三个关键字sep、end、file后,如果字典里还有值,则证明是多余的关键字,程序需要报错。第二在pop的时候,如果这三个参数如果在调用函数的时候指定了值,就用指定的值,如果没有指定值则用程序中指定的默认值。

从下面的例子就可以看出,使用多余的关键字,程序会报错。

print30('hello','world','healthy',sep='&',ppp='33',hhh='44')

Traceback (most recent call last):
 File "E:/12homework/12homework.py", line 15, in <module>
   print30('hello','world','healthy',sep='&',ppp='33',hhh='44')
 File "E:/12homework/12homework.py", line 7, in print30
raise TypeError('extra keywords:{}'.format(kargs))
TypeError: extra keywords:{'ppp': '33', 'hhh': '44'}



Python爱好者社区历史文章大合集

Python爱好者社区历史文章列表(每周append更新一次)

福利:文末扫码立刻关注公众号,“Python爱好者社区”,开始学习Python课程:

关注后在公众号内回复“课程”即可获取:

小编的Python入门免费视频课程!!!

【最新免费微课】小编的Python快速上手matplotlib可视化库!!!

崔老师爬虫实战案例免费学习视频。

陈老师数据分析报告制作免费学习视频。

玩转大数据分析!Spark2.X+Python 精华实战课程免费学习视频。


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存