{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"inputHidden": false
},
"source": [
"# 4.3 函数的参数传递"
]
},
{
"cell_type": "markdown",
"metadata": {
"inputHidden": false
},
"source": [
"当函数定义里有多个参数时,其参数的传递形式主要有以下五种:<font color=Red>__位置传递__</font>、<font color=Red>__关键字__</font>、<font color=Red>__默认值传递__</font>、<font color=Red>__包裹传递__</font>和<font color=Red>__解包裹传递__</font>。"
]
},
{
"cell_type": "markdown",
"metadata": {
"inputHidden": false
},
"source": [
"Python 中一切皆为<font color=Red>__对象__</font>,数字是对象,列表是对象,函数也是对象。而变量是对象的一个<font color=Red>__引用__</font>,对象的操作都是通过引用来完成的。\n",
"\n",
"函数调用过程中,<font color=Red>__传递的是对象__</font>。参数的传递本质上是名字到对象的<font color=Red>__绑定过程__</font>。"
]
},
{
"cell_type": "markdown",
"metadata": {
"inputHidden": false
},
"source": [
"默认情况下,参数可以按位置或显式关键字传递给 Python 函数。为了让代码易读、高效,最好限制参数的传递方式,这样,开发者只需查看函数定义,即可确定参数项是仅按位置、按位置或关键字,还是仅按关键字传递。\r\n",
"\r\n",
"函数定义如下:下:下:"
]
},
{
"cell_type": "raw",
"metadata": {
"inputHidden": false
},
"source": [
"def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):\n",
" ----------- ---------- ----------\n",
" | | |\n",
" | 位置或关键字参数 |\n",
" | - 仅关键字参数\n",
" -- 仅位置参数"
]
},
{
"cell_type": "markdown",
"metadata": {
"inputHidden": false
},
"source": [
"/ 和 * 是可选的。这些符号表明形参如何把参数值传递给函数:位置、位置或关键字、关键字。 \n",
"关键字形参也叫作命名形参。函数定义中未使用 / 和 * 时,参数可以按位置或关键字传递给函数。"
]
},
{
"cell_type": "markdown",
"metadata": {
"inputHidden": false
},
"source": [
"## 4.3.1 位置传递"
]
},
{
"cell_type": "markdown",
"metadata": {
"inputHidden": false
},
"source": [
"位置固定,参数传递时按照形式参数<font color=Red>__定义的顺序__</font>提供实际参数。\n",
"其优点是使用方便,缺点是当参数数目较多时,参数对应容易混淆。且要求传入参数的数量必须和定义函数时参数的数量相同。"
]
},
{
"cell_type": "markdown",
"metadata": {
"inputHidden": false
},
"source": [
"仅限位置 时,形参的顺序很重要,且这些形参不能用关键字传递。仅限位置形参应放在 / (正斜杠)前。/ 用于在逻辑上分割仅限位置形参与其它形参。如果函数定义中没有 /,则表示没有仅限位置形参。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def fun(name, city, hobby):\n",
" '''接收三个字符串作为参数,返回一个进行自我介绍的字符串'''\n",
" return '我的名字是{},来自{},我的爱好是{}。 '.format(name, city, hobby) \n",
"\n",
"\n",
"n,c,h = input().split() # 根据空格切分输入字符串,分别赋值给n,c,h \n",
"print(fun(n,c,h)) # 根据实参变量n,c,h的顺序传递参数或值给形参name, city, hobby"
]
},
{
"cell_type": "markdown",
"metadata": {
"inputHidden": false
},
"source": [
"调用fun()函数时,将实际参数n、c、h这三个字符串对象按顺序赋值给形式参数name、city、hobby,或者说将三个变量标签name、city、hobby按出现的顺序分别贴在\"夏琦\"、\"武汉\"、\"羽毛球\"这三个字符串对象上。"
]
},
{
"cell_type": "markdown",
"metadata": {
"inputHidden": false
},
"source": [
"在定义fun()函数时,参数name、city、hobby尚未绑定对象,没有值。\n",
"\n",
"只有经过函数调用,才会把调用时的参数值赋值给形式参数,函数体中的变量才有了值。"
]
},
{
"cell_type": "markdown",
"metadata": {
"inputHidden": false
},
"source": [
"## 4.3.2 关键字传递"
]
},
{
"cell_type": "markdown",
"metadata": {
"inputHidden": false
},
"source": [
"关键字传递是指,在函数调用时<font color=Red>__提供__</font>实际参数所对应的<font color=Red>__形式参数__</font>的名称,根据<font color=Red>__参数名称来传递参数__</font>。\n",
"\n",
"关键字并不需要遵守位置的对应关系。其优点是明确标示实际参数和形式参数的对应关系,参数的书写顺序更灵活。缺点是增加了函数调用时的代码书写量。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def fun(name, city, hobby):\n",
" return '我的名字是{},来自{},我的爱好是{}'.format(name, city, hobby) \n",
"\n",
"\n",
"n,c,h = input().split() # 切分字符串,分别赋值给n,c,h \n",
"print(fun(hobby = h, city = c,name = n)) # 根据关键字name, city, hobby来传递参数,关键字传递时顺序无关 "
]
},
{
"cell_type": "markdown",
"metadata": {
"inputHidden": false
},
"source": [
"位置传递和关键字传递<font color=Red>__可以混用__</font>。但要注意的是,混用时,按<font color=Red>__位置传递__</font>的参数要出现在按<font color=Red>__关键字传递__</font>的参数<font color=Red>__之前__</font>。否则,编译器无法明确知道除关键字以外的参数出现的顺序。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def fun(name, city, hobby):\n",
" return '我的名字是{},来自{},我的爱好是{}'.format(name, city, hobby)\n",
"\n",
"n,c,h = input().split() # 切分字符串,分别赋值给n,c,h\n",
"print(fun(n, hobby = h, city = c)) # 位置参数n出现在关键字参数h和c之前 "
]
},
{
"cell_type": "markdown",
"metadata": {
"inputHidden": false
},
"source": [
"在定义函数时,我们可以在参数列表中用“/”设置强制位置参数(positional-only arguments)。用“*”设置命名关键字参数。 \n",
"所谓强制位置参数,就是调用函数时只能按照参数位置来接收参数值的参数;而命名关键字参数只能通过“参数名=参数值”的方式来传递和接收参数,大家可以看看下面的例子。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def fun(name, city, hobby, /): # /前面的参数是强制位置参数\n",
" \"\"\"接收三个字符串作为位置参数,返回一个进行自我介绍的字符串\"\"\"\n",
" return '我的名字是{},来自{},我的爱好是{}。 '.format(name, city, hobby)\n",
"\n",
"\n",
"print(fun('夏琦', '武汉', '羽毛球')) # 可以按位置传递参数\n",
"# 输出 我的名字是夏琦,来自武汉,我的爱好是羽毛球。 "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def fun(name, city, hobby, /): # /前面的参数是强制位置参数\n",
" \"\"\"接收三个字符串作为位置参数,返回一个进行自我介绍的字符串\"\"\"\n",
" return '我的名字是{},来自{},我的爱好是{}。 '.format(name, city, hobby)\n",
"\n",
"\n",
"# 下面的代码会产生TypeError错误,错误信息提示“强制位置参数是不允许给出参数名的”\n",
"# TypeError: fun() got some positional-only arguments passed as keyword arguments: 'city, hobby'\n",
"n, c, h = '夏琦', '武汉', '羽毛球'\n",
"print(fun(n, hobby=h, city=c)) # 传入一个位置参数和两个关键字参数"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def fun(name, *, city, hobby): # *后面的参数是强制关键字参数\n",
" \"\"\"接收三个字符串,一个位置参数两个关键字参数,返回一个进行自我介绍的字符串\"\"\"\n",
" return '我的名字是{},来自{},我的爱好是{}。 '.format(name, city, hobby)\n",
"\n",
"\n",
"n, c, h = '夏琦', '武汉', '羽毛球'\n",
"print(fun(n, hobby=h, city=c)) # 传入一个位置参数和两个关键字参数\n",
"# 输出 我的名字是夏琦,来自武汉,我的爱好是羽毛球。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def fun(name, *, city, hobby): # *后面的参数是强制关键字参数\n",
" \"\"\"接收三个字符串,一个位置参数两个关键字参数,返回一个进行自我介绍的字符串\"\"\"\n",
" return '我的名字是{},来自{},我的爱好是{}。 '.format(name, city, hobby)\n",
"\n",
"\n",
"# 下面的代码会产生TypeError错误,错误信息提示“函数只能接收一个位置参数,但传递了3个位置参数”\n",
"# TypeError: fun() takes 1 positional argument but 3 were given\n",
"print(fun('夏琦', '武汉', '羽毛球')) # 传入三个位置参数"
]
},
{
"cell_type": "markdown",
"metadata": {
"inputHidden": false
},
"source": [
"## 4.3.3 默认值传递"
]
},
{
"cell_type": "markdown",
"metadata": {
"inputHidden": false
},
"source": [
"在定义函数的时候,使用形如city='武汉'的方式,可以给形式参数赋予<font color=Red>_默认值(default)__</font>。在函数调用时,如果该参数得到传入值,按传入值进行计算,否则使用默认值。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def fun(name, city, hobby='唱歌'):\n",
" return '我的名字是{},来自{},我的爱好是{}。'.format(name, city, hobby)\n",
"\n",
"n,c = input().split() # 切分字符串,分别赋值给n,c,例如输入:夏琪 武汉\n",
"print(fun(n, c)) # 默认值参数可以不传值 \n",
"# 输出: 我的名字是夏琪,来自武汉,我的爱好是唱歌。\n",
"\n",
"print(fun(n, c, '游泳')) # 传值时,默认值会被传入的值替换 \n",
"# 输出: 我的名字是夏琪,来自武汉,我的爱好是游泳。"
]
},
{
"cell_type": "markdown",
"metadata": {
"inputHidden": false
},
"source": [
"可以用列表存储输入,再用星号*解包传递给函数,可做到传递任意数量的参数。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def fun(name, city, hobby='唱歌'):\n",
" return '我的名字是{},来自{},我的爱好是{}。'.format(name, city, hobby)\n",
"\n",
"ls = input().split() # 切分字符串得到列表,例如输入:夏琪 武汉,得到['夏琪', '武汉']\n",
"print(fun(*ls)) # ['夏琪', '武汉']解包为:'夏琪', '武汉'\n",
"# 输出: 我的名字是夏琪,来自武汉,我的爱好是唱歌。\n",
"\n",
"\n",
"ls = input().split() # 切分字符串得到列表,例如输入:夏琪 武汉 游泳,得到['夏琪', '武汉', '游泳']\n",
"print(fun(*ls)) # ['夏琪', '武汉', '游泳']解包为:'夏琪', '武汉', '游泳'\n",
"# 输出: 我的名字是夏琪,来自武汉,我的爱好是游泳。"
]
},
{
"cell_type": "markdown",
"metadata": {
"inputHidden": false
},
"source": [
"需要注意的是,<font color=Red>__默认值参数__</font>必须放在必选参数<font color=Red>__之后__</font>。当函数的参数有多个时,默认值参数必须在后面,非默认值参数在前面。\n",
"\n",
"也就是说,<font color=Red>__一旦__</font>出现了带默认值的参数,<font color=Red>__后面的其他参数都必须带默认值了__</font>。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def fun( city,name='夏琪', hobby='唱歌'): #语法错误,city 是非默认值参数,出现在默认值参数hobby的前面了\n",
" return '我的名字是{},来自{},我的爱好是{}。'.format(name, city, hobby)\n",
"\n",
"\n",
"n,c,h = input().split() # 切分字符串,分别赋值给n,c,h \n",
"print(fun(n,c,h)) # 调用函数"
]
},
{
"cell_type": "markdown",
"metadata": {
"inputHidden": false
},
"source": [
"<font face='楷体' color='red' size=5> 练一练 </font>\n",
"\n",
"利用Python中支持默认值参数的特性,修改上一节里定义的幂函数power(x,n),使其默认计算x的平方。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#修改后的power(x,n)函数\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"inputHidden": false
},
"source": [
"需要注意的是,默认值参数可以指向不可变对象,如整型(int)、字符串(string)、浮点型(float)、元组(tuple)等,不能指向字典(dict)和列表(list)等可变对象。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def fun(x, ls = []): # 这里默认参数ls指向了空列表,列表是可变对象\n",
" ls.append(x) # 在列表ls的末尾追加一个元素\n",
" return ls # 返回ls\n",
"\n",
"print(fun(1)) # 第一次调用,输出 [1] \n",
"print(fun(3)) # 第二次调用,输出 [1, 3] \n",
"print(fun(5)) # 第三次调用,输出 [1, 3, 5] "
]
},
{
"cell_type": "markdown",
"metadata": {
"inputHidden": false
},
"source": [
"按函数调用规则,每次函数调用时,形参都会被重新赋值。即在三次调用中,形参x分别被赋值为1,3,5,带缺省值的形参ls每次应该被重新赋值为空列表。\n",
"\n",
"但程序的运行结果表明,在多次调用过程中,<font color=Red>__ls并没有被重新赋值为空列表__</font>,导致ls中的元素累积下来。\n",
"分析其原因,ls是可变对象,在函数定义时被创建,<font color=Red>__其后所有函数调用都引用这个列表对象__</font>。Python规定参数传递都是传递的引用,也就是传递给函数的是原变量实际所指向的内存空间。而append()方法并不会改变列表的内存空间,也就是说不会重新创建列表对象,只是向其中增加元素。所以每次调用该函数时,一直引用函数定义时创建的列表对象ls,<font color=Red>__导致元素累积__</font>。"
]
},
{
"cell_type": "markdown",
"metadata": {
"inputHidden": false
},
"source": [
"如果一定要用这种方法的话,可以做如下修改:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def fun(x, ls = None): # ls为默认值参数,设为不可变对象None\n",
" if ls is None: # 若ls值为None,说明是重新调用,将列表置为空\n",
" ls = []\n",
" ls.append(x)\n",
" return ls \n",
"\n",
"\n",
"print(fun(1)) # 第一次调用,输出 [1] \n",
"print(fun(3)) # 第二次调用,输出 [3] \n",
"print(fun(5)) # 第三次调用,输出 [5]"
]
},
{
"cell_type": "markdown",
"metadata": {
"inputHidden": false
},
"source": [
"## 4.3.4 包裹传递"
]
},
{
"cell_type": "markdown",
"metadata": {
"inputHidden": false
},
"source": [
"包裹传递也称为不定参数传递,用于在定义函数时不能确定函数调用时会传递多少个参数时使用。函数每次调用时,传递的参数数量可以不同。"
]
},
{
"cell_type": "markdown",
"metadata": {
"inputHidden": false
},
"source": [
"1. 当传入参数是位置传递时,所有传入参数被收集打包合并成一个元组,再传递给函数。"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(1, 3, 5, 7, 9)\n",
"25\n",
"(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)\n",
"55\n",
"(1, 2, 'hello', 3.45, 6)\n",
"12.45\n"
]
}
],
"source": [
"def add(*number):\n",
" \"\"\"接收不确定数量位置传递的参数,合并为一个元组number在函数体内使用\"\"\"\n",
" print(number) # 查看参数类型,元组\n",
" result = 0\n",
" for i in number:\n",
" if type(i) in (int, float): # 对参数进行了类型检查(数值型的才能求和)\n",
" result = result + i\n",
" return result\n",
"\n",
"\n",
"if __name__ == '__main__':\n",
" print(add(1, 3, 5, 7, 9))\n",
" print(add(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))\n",
" print(add(1, 2, 'hello', 3.45, 6)) # 12.45"
]
},
{
"cell_type": "markdown",
"metadata": {
"inputHidden": false
},
"source": [
"1. 当传入参数是关键字传递时,所有传入的关键字参数被收集打包合并成一个字典,再传递给函数。"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'a': 1, 'b': 3, 'c': 5}\n",
"9\n",
"{'m': 1, 'n': 2, 'o': 3, 'p': 4, 'q': 5, 'i': 6, 'j': 7, 'k': 8}\n",
"36\n"
]
}
],
"source": [
"def add(**number):\n",
" \"\"\"接收不确定数量的关键字参数,合并为一个字典number在函数体内使用\"\"\"\n",
" print(number)\n",
" return sum(number.values())\n",
"\n",
"\n",
"if __name__ == '__main__':\n",
" print(add(a=1, b=3, c=5))\n",
" print(add(m=1, n=2, o=3, p=4, q=5, i=6, j=7, k=8))\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"inputHidden": false
},
"source": [
"参数列表中的**kwargs可以接收0个或任意多个关键字参数 \n",
"调用函数时传入的关键字参数会组装成一个字典(参数名是字典中的键,参数值是字典中的值) \n",
"如果一个关键字参数都没有传入,那么kwargs会是一个空字典 "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def foo(*args, **kwargs):\n",
" print(args)\n",
" print(kwargs)\n",
"\n",
"\n",
"foo(3, 2.1, True, name='骆昊', age=43, gpa=4.95)\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"inputHidden": false
},
"source": [
"## 4.3.5 解包裹传递"
]
},
{
"cell_type": "markdown",
"metadata": {
"inputHidden": false
},
"source": [
"“\\*”“\\*\\*”也可以以函数调用时使用,此时称为解包裹传递。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def add(a, b, c):\n",
" \"\"\"接收三个位置传递参数,返回其加和\"\"\"\n",
" return a + b + c\n",
"\n",
"\n",
"if __name__ == '__main__':\n",
" num1 = (1, 3, 5)\n",
" print(add(*num1))\n",
" num2 = {'a': 1, 'b': 2, 'c': 3}\n",
" print(add(**num2))"
]
},
{
"cell_type": "markdown",
"metadata": {
"inputHidden": false
},
"source": [
"函数调用总是会给形参列表中列出的所有形参赋值,或是用位置参数,或是用关键字参数,或是用默认值。 如果存在 \"*identifier\" 这样的形式,它会被初始化为一个元组来接收任何额外的位置参数,默认为一个空元组。 如果存在 \"**identifier\" 这样的形式,它会被初始化为一个新的有序映射来接收任何额外的关键字参数,默认为一个相同类型的空映射。 \n",
"在 \"*\" 或 \"*identifier\" 之后的形参都是仅限关键字形参因而只能通过关键字参数传入。 \n",
"在 \"/\" 之前的形参都是仅限位置形参因而只能通过位置参数传入。"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.5"
}
},
"nbformat": 4,
"nbformat_minor": 4
}