《手把手陪您学Python》27——自定义函数的参数

在上一篇《手把手陪您学Python》26——自定义函数中,我们学习了Python自定义函数的作用,以及自定义函数的语法格式。在本篇中,我们将对自定义函数的参数进行讲解。

自定义函数的参数与我们之前学习的内置函数的参数没有什么区别,都是在函数名后面括号中的部分,并且有的函数的参数可以为空,有的函数的参数可以是一个或者多个。

比如,我们上一篇投骰子中自定义的两个函数play()和win(A, B),一个参数为空,一个有两个参数。

此外,对于同一个函数来说,参数的数量有时也是不同的。比如我们常用的print()函数,既可以只将打印内容作为唯一的参数,也可以对间隔符参数sep,以及结束符参数end进行定义,此时print()函数就有了多个参数。

但这种情况并不是说print()函数的参数数量是可变的,而是print()函数中部分参数的值是默认的。比如sep参数的默认值是空格‘ ’,而end参数的默认值则是换行符'\n'。

所以,当我们使用print()函数参数的默认值时,print()函数就只需要输入一个参数值就可以了;而当我们要对有默认值的参数进行修改时,就需要输入多个参数值,就好像print()函数的参数数量是变化的一样。

虽然print()函数是内置函数,但我们自定义的函数也可以通过设置默认参数值的方式达到同样的效果。

上面我们讲到的函数参数的数量,以及默认参数值的例子,都属于函数参数的应用范畴。此外,还有关键字参数以及位置参数等类型,下面我们就来详细了解一下。

1、参数的传递

上一篇中我们说到,在程序中定义好函数后,使用函数的过程叫做函数的调用,而程序调用的第一步就是参数的传递,也就是把我们希望函数使用的原始数据提供给函数的过程。

看一个最简单的实例。

In [1]: def qiuhe(a, b):   # 为了便于理解,这里的变量名都是拼音
            he = a + b
            return he
            
        a = 2
        b = 5
        print("a+b的结果是{}。".format(qiuhe(a, b)))

Out[1]: a+b的结果是7。

在上面的实例中,我们首先定义了qiuhe(a, b)这样一个函数,之后确定了a、b的值(更形象的方式是使用input()函数输入,这里只为了展示而简化了)。在打印结果时,需要调用函数qiuhe(a, b)进行计算。这时,我们将原始数据a、b的值提供给qiuhe(a, b)函数的过程,就是参数的传递。

2、参数的命名

刚才参数传递的概念非常容易理解,也非常直观。但绝大多数情况下,参数的传递并不是这么直观的。

这里说的直观是指原始数据的变量名称(上例中的a和b)正好与函数的参数名称(上例中的a和b)完全相同。而是像我们上一篇中hello(text)函数那个示例一样,参数的名称是text,而原始数据的变量名称是a。

当然,刚才的实例也完全可以写成原始数据的变量名称与函数的参数名称不一致的形式,而这种不一致反而是我们更常用的方式

因为在一个非常复杂的项目中,参数的命名规则是非常讲求准确的,可不能像我们示例这种,随便找个abcxyz就能当参数名称的。如果这样的话,代码的可读性就会非常的差,别说使用者了,就是作者可能都记不清之前定义的abc参数都代表什么含义。

就好像print()函数一样,如果把间隔符参数命名成a,结束符参数命名成b,可能我们每次都要查看ab的含义,才能知道我们要往哪个参数传递值了。但print()函数的参数名称sep和end就非常清晰,很容易就能了解各个参数的含义,也就方便使用了。

在我们自定义函数的时候也是一样,一定要将参数的含义写清楚,让使用者一眼就能看出来各个参数的含义。哪怕像我们上面那种非常简单的函数也一样可以做到。

In [2]: def qiuhe(beijiashu, jiashu):   # 为了便于理解,这里的变量名都是拼音
            he = beijiashu + jiashu
            return he
            
        a = 2
        b = 5
        print("a+b的结果是{}。".format(qiuhe(a, b)))

Out[2]: a+b的结果是7。

这里,我们将qiuhe()函数的参数名称改为了beijiashu和jiashu,很容易就能知道函数的各个参数都是什么含义。

也许在这样简单的实例中作用并不明显,但也请大家一定要养成给参数准确命名的习惯,否则一定会遇到因为忘记参数含义而后悔的时候。就好像我们在电脑里建立了无数“新建文件夹”一样,等找文件的时候就会手足无措、后悔当初了,但也只能硬着头皮一个个文件地去找,或者一个参数一个参数去看一样了。

3、参数的类型

解决了参数命名的问题,但又带来了新的问题,那就是原始数据的变量名称和函数的参数名称不一样了,这时参数是如何传递的呢?

这就是这篇文章的重点内容了,也就是我们所说的参数的类型,包括位置参数、关键字参数和不定长参数。

a、位置参数

位置参数是我们最常见的参数类型,位置参数必须以正确的位置(顺序)和相同的数量传入函数进行调用。

上面所说的情况就是位置参数的一种体现。也就是原始数据的变量名称虽然与函数的参数名称不同,但只要在调用函数时,提供位置对应且数量相同的变量作为函数的参数就可以,而不需要名称相同。

在上例中,函数的参数名称是beijiashu和jiashu,而调用函数时提供的变量名称是a和b。虽然名称不同,但数量都是2个,而且a的位置和beijiashu的位置相同,b的位置和jiashu的位置相同。

那么在调用函数时,就把a的值传递给了beijiashu,把b的值传递给了jiashu,实现了参数值的按“位置”传递。

对于a、b以及beijiashu、jiashu也有他们自己规范的叫法。

我们将定义函数时,小括号中的参数(即beijiashu、jiashu),叫做“形参”,是用来接收参数用的,在函数内部作为变量使用;

而将调用函数时,小括号中的参数(即a、b),叫做“实参”,是用来把数据传递到函数内部用的。

也就是当参数的类型为位置参数时,形参和实参的名称不要求相同,但位置要一一对应,数量要完全相同,否则就会报错。

下面看几个实例。

In [3]: def tixingmianji(shangdi, xiadi, gao):   # 函数名称和参数是不是命名的很准确?
            mianji = (shangdi + xiadi) * gao / 2
            return mianji
            
        a = 2
        b = 5
        h = 3
        print("梯形面积等于{}。".format(tixingmianji(a, b, h)))

Out[3]: 梯形面积等于10.5。

在这个实例中,上底、下底、高都按照我们指定的位置传递给了tixingmianji()函数的对应参数,而且数量相同,所以得到了我们期望的结果。

但如果数量相同,而位置对应错误了,虽然程序不会报错,但得到的结果就是错误的了。

In [4]: def tixingmianji(shangdi, xiadi, gao):
            mianji = (shangdi + xiadi) * gao / 2
            return mianji
            
        a = 2
        b = 5
        h = 3
        print("梯形面积等于{}。".format(tixingmianji(a, h, b)))

Out[4]: 梯形面积等于12.5。

而如果干脆连参数的数量都不对,那么程序就会直接报错。

In [5]: def tixingmianji(shangdi, xiadi, gao):
            mianji = (shangdi + xiadi) * gao / 2
            return mianji
            
        a = 2
        b = 5
        h = 3
        print("梯形面积等于{}。".format(tixingmianji(a, b)))

Out[5]: ---------------------------------------------------------------------------
        TypeError                                 Traceback (most recent call last)
         in 
              6 b = 5
              7 h = 3
        ----> 8 print("梯形面积等于{}。".format(tixingmianji(a, b)))
        
        TypeError: tixingmianji() missing 1 required positional argument: 'gao'

在错误类型中,也是很智能地提示了错误的原因,是缺少了一个位置参数(positional argument)‘gao’。

b、关键字参数

位置参数使用起来非常方便,但是当参数值过多时,要记住每一个参数的位置也不是一件容易的事,有没有更简便准确一点的方法呢?

答案是有的,这就是关键字参数。

使用关键字参数时,允许调用函数时参数的顺序与定义时的不一样,但要求是参数名的使用必须要准确,因为Python解释器能够用参数名匹配参数值。

这也是我们在上面参数的命名中建议的,要将参数名称命名准确了,不仅便于阅读理解参数的含义,也为了作为关键字参数时方便调用。

比如,我们tixingminaji()函数的参数名称就非常容易识别和使用,无论什么情况,什么顺序都能够很容易地为这几个参数传递值。

In [6]: def tixingmianji(shangdi, xiadi, gao):
            mianji = (shangdi + xiadi) * gao / 2
            return mianji
            
        a = 2
        b = 5
        h = 3
        print("梯形面积等于{}。".format(tixingmianji(gao = h, xiadi = b, shangdi = a)))

Out[6]: 梯形面积等于10.5。

当shangdi、xiadi、gao作为关键字参数,在调用时,无需考虑顺序问题,只需要将所有的参数都给传递一遍值就可以了。

关键字参数除了可以解决不好记忆位置的问题外,当其与位置参数共同使用时,还可以让我们定义出与print()效果一样的、带有默认值的自定义函数。

In [7]: def tixingmianji(shangdi, xiadi, gao = 3):
            mianji = (shangdi + xiadi) * gao / 2
            return mianji
            
        a = 2
        b = 5
        print("梯形面积等于{}。".format(tixingmianji(a, b)))

Out[7]: 梯形面积等于10.5。

在这个实例中,我们将gao的默认值定义为3。那么,只要提供shangdi和xiadi的参数值,就能够计算出梯形的面积了。此时,shangdi和xiadi就是位置参数,需要按照位置和数量提供参数值。

如果我们还想利用gao的默认值为3的这个函数,但要计算的梯形的高却为4,那么就只需要给gao再传递一个值即可。

就像我们在使用print()函数时,不需要修改sep和end(关键字参数)的默认值时,只需要提供要打印的内容(位置参数)即可;而需要自定义sep和end(关键字参数)的值时,就需要使用关键字参数的方式,给sep和end传递参数值了。

In [8]: def tixingmianji(shangdi, xiadi, gao = 3):
            mianji = (shangdi + xiadi) * gao / 2
            return mianji
            
        a = 2
        b = 5
        h = 4
        print("梯形面积等于{}。".format(tixingmianji(a, b, gao = h)))

Out[8]: 梯形面积等于14。

需要说明的有两点:

一是关键字参数的位置必须要在位置参数的后面,否则会报错(positional argument follows keyword argument),无论是形参还是实参都要遵守这条规则;

二是如果位置参数的位置还是不方便记忆,那就可以都改为关键字参数进行使用,此时就不需要考虑位置问题了。

上面两种情况,大家可以自行编写代码尝试验证。对于第一点,还可以将xiadi作为关键字参数,看一看位置参数和关键字参数的不同位置和顺序会带来怎样的影响。

c、不定长参数

最后一种参数类型是不定长参数,这种参数类型我们目前用到的情况不多,所以只是简单了解一下就行。

不定长参数是一种能够处理比定义函数、确定参数时更多的参数的一种参数类型。

这句话说起来有些拗口,实际上就是有一种函数,虽然参数的数量有限,但是能够处理比定义时多的多的参数。

一种情况是在参数的前面加一个*号,加了*号的参数会以元组的形式接收传来的多个变量。

In [9]: def dayin(canshu1, canshu2, *qitacanshu):    # qitacanshu为接收多于前面两个参数的参数
            print(canshu1, canshu2, qitacanshu, sep = '\n')    # 变量名称不需要加*

        a = 5
        b = 10
        c = 25
        d = 50
        e = 100
        dayin(a, b, c, d, e)

Out[9]: 5
        10
        (25, 50, 100)

与位置参数不同的是,可以不给加*号的参数传递值,此时,这个参数生成的就是空元组。也就是说,虽然看上去的参数数量不足,但实际上不会报错。

In [10]: def dayin(canshu1, canshu2, *qitacanshu):
             print(canshu1, canshu2, qitacanshu, sep = '\n')
             
         a = 5
         b = 10
         dayin(a, b)   # 看上去参数好像少了一个,但不会报错

Out[10]: 5
         10
         ()

另一种情况是在参数的前面加两个*号,此时加了**号的参数会以字典的形式接收传来的多个变量。

In [11]: def dayin(canshu1, canshu2, **qitacanshu):    # qitacanshu为接收多于前面两个参数的参数
         print(canshu1, canshu2, qitacanshu, sep = '\n')    # 变量名称不需要加**

         a = 5
         b = 10
         c = 25
         d = 50
         e = 100
         dayin(a, b, key1 = c, key2 = d, key3= e)   # 因为是字典的形式,所以要提供key值

Out[11]: 5
         10
         {'key1': 25, 'key2': 50, 'key3': 100}

虽然不定长参数在我们目前的学习阶段不会接触的太多,但因为它能够生成非常常用的两种数据结构——元组和字典,因此在某些情况下,能够发挥出非常重要的作用。

以上就是我们对自定义函数的参数的介绍,在了解了函数参数的传递方法以及命名方式后,着重讲解了位置参数和关键字参数的应用。虽然对不定长参数的讲解比较基础,但这种初步的概念,有可能会给我们日后解决问题提供一种新的思路。

下一篇,我们还会继续自定义函数的讲解,主要是函数返回值的介绍和应用,敬请关注。

 

 


感谢阅读本文!如有任何问题,欢迎留言,一起交流讨论^_^

要阅读《手把手陪您学Python》系列文章的其他篇目,请关注公众号点击菜单选择,或点击下方链接直达。

《手把手陪您学Python》1——为什么要学Python?

《手把手陪您学Python》2——Python的安装

《手把手陪您学Python》3——PyCharm的安装和配置

《手把手陪您学Python》4——Hello World!

《手把手陪您学Python》5——Jupyter Notebook

《手把手陪您学Python》6——字符串的标识

《手把手陪您学Python》7——字符串的索引

《手把手陪您学Python》8——字符串的切片

《手把手陪您学Python》9——字符串的运算

《手把手陪您学Python》10——字符串的函数

《手把手陪您学Python》11——字符串的格式化输出

《手把手陪您学Python》12——数字

《手把手陪您学Python》13——运算

《手把手陪您学Python》14——交互式输入

《手把手陪您学Python》15——判断语句if

《手把手陪您学Python》16——循环语句while

《手把手陪您学Python》17——循环的终止

《手把手陪您学Python》18——循环语句for

《手把手陪您学Python》19——第一阶段小结

《手把手陪您学Python》20——列表

《手把手陪您学Python》21——元组

《手把手陪您学Python》22——字典

《手把手陪您学Python》23——内置序列函数

《手把手陪您学Python》24——集合

《手把手陪您学Python》25——列表推导式

《手把手陪您学Python》26——自定义函数

For Fans:关注“亦说Python”公众号,回复“手27”,即可免费下载本篇文章所用示例语句。

亦说Python——Python爱好者的学习分享园地
 

你可能感兴趣的:(《手把手陪您学Python》,python)