一. 形参和实参
形参:函数定义里的参数。
实参:调用函数时传入的参数。
>>> def welcome(param1,param2):
... print(param1,', ',param2)
...
>>>
>>> welcome('Hello','World')
Hello , World
其中,param1 和 param2 是函数的形参,而在函数 welcome() 被调用时,传入的('Hello'
和 'Python'
)则是实参。
【注】
- 在函数体内都是对形参进行操作,不能操作实参,即对实参做出更改。
- 作为实参传入函数的变量名称和函数定义里形参的名字没有关系。
二. 参数传递
在 python 中,类型属于对象,变量是没有类型的:
a=[1,2,3]
a="zth"
以上代码中,[1,2,3] 是 List 类型,"zth" 是 String 类型,而变量 a 是没有类型,她仅仅是一个对象的引用(一个指针),可以是指向 List 类型对象,也可以是指向 String 类型对象。
1. 可更改(mutable)与不可更改(immutable)对象
在 python 中,strings, tuples, 和 numbers 是不可更改的对象,而 list,dict 等则是可以修改的对象。
-
不可变类型:变量赋值 a=5 后再赋值 a=10,这里实际是新生成一个 int 值对象 10,再让 a 指向它,而 5 被丢弃,不是改变a的值,相当于新生成了a。
-
可变类型:变量赋值 la=[1,2,3,4] 后再赋值 la[2]=5 则是将 list la 的第三个元素值更改,本身la没有动,只是其内部的一部分值被修改了。
2. python 函数的参数传递:
-
不可变类型:类似 c++ 的值传递,如 整数、字符串、元组。如fun(a),传递的只是a的值,没有影响a对象本身。比如在 fun(a)内部修改 a 的值,只是修改另一个复制的对象,不会影响 a 本身。
-
可变类型:类似 c++ 的引用传递,如 列表,字典。如 fun(la),则是将 la 真正的传过去,修改后fun外部的la也会受影响
python 中一切都是对象,严格意义我们不能说值传递还是引用传递,我们应该说传不可变对象和传可变对象。
python 传不可变对象实例
>>> def ChangeInt(a):
... a = 10
...
>>> b =2
>>> b
2
实例中有 int 对象 2,指向它的变量是 b,在传递给 ChangeInt 函数时,按传值的方式复制了变量 b,a 和 b 都指向了同一个 Int 对象,在 a=10 时,则新生成一个 int 值对象 10,并让 a 指向它。
传可变对象实例
可变对象在函数里修改了参数,那么在调用这个函数的函数里,原始的参数也被改变了。
>>> def channgeme(mylist):
... "修改传入的列表"
... mylist.append([1,2,3,4])
... print("函数内取值:",mylist)
... return
...
>>>
>>> mylist = [7,8,9]
>>> channgeme(mylist)
函数内取值: [7, 8, 9, [1, 2, 3, 4]]
>>> print("函数外取值:",mylist)
函数外取值: [7, 8, 9, [1, 2, 3, 4]]
>>>
传入函数的和在末尾添加新内容的对象用的是同一个引用。
三. 参数
调用函数时可以使用以下参数类型:
- 位置参数
- 关键字参数
- 默认参数
- 可变参数
- 组合参数
1. 位置参数
位置参数须以正确的顺序传入函数。调用时的数量必须和声明时的一样。
>>> def printme( str):
... print(str)
... return
...
>>>
>>> printme("Hello World") # 一个参数
Hello World
>>>
>>>
>>> printme() # 没有参数
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: printme() missing 1 required positional argument: 'str'
>>>
>>>
>>> printme("Hello" ," World") # 多于一个参数
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: printme() takes 1 positional argument but 2 were given
2. 关键字参数
关键字参数和函数调用关系紧密,函数调用使用关键字参数来确定传入的参数值。
使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。
>>> def personinfo(name,age):
... print('姓名:',name)
... print('年龄:',age)
... return
...
>>>
>>> personinfo('zth',20) # 按参数顺序传入参数
姓名: zth
年龄: 20
>>>
>>>
>>> personinfo(name='zth',age=20) # 按参数顺序传入参数,并指定参数名
姓名: zth
年龄: 20
>>>
>>>
>>> personinfo(age = 20, name = 'zth') # 不按参数顺序传入参数,并指定参数名
姓名: zth
年龄: 20
>>>
>>>
>>> personinfo(20,'zth') # 不按参数顺序传入参数
姓名: 20
年龄: zth
>>>
>>>
>>> personinfo( name = 'zth') # 传入部分参数,并指定参数名
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: personinfo() missing 1 required positional argument: 'age'
>>>
>>>
>>> personinfo( 'zth') # 传入部分参数,不指定参数名
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: personinfo() missing 1 required positional argument: 'age'
【注】
关键字参数必须在位置参数之后,所有传递的关键字参数都必须与函数接受的某个参数匹配,并且它们的顺序不重要。
3. 默认参数
定义函数时,可以使用赋值运算符(=
)为参数指定一个默认值。
调用函数时,如果没有传递参数,则会使用默认参数。
注意: 如果参数没有默认值,在调用时必需为其指定一个值;如果参数有默认值,那么在调用时值是可选的,如果为其提供了一个值,将会覆盖默认值。
>>> def personinfo(name,age=23):
... print("姓名:",name)
... print("年龄:",age)
... return
...
>>>
>>>
>>> personinfo('zth') # 还用默认age
姓名: zth
年龄: 23
>>>
>>> personinfo('zth',20) # 传入 age 参数
姓名: zth
年龄: 20
>>>
默认参数一定要放在非默认参数后面:
>>> def personinfo(age=23,name):
... print("姓名:",name)
... print("年龄:",age)
... return
...
File "<stdin>", line 1
SyntaxError: non-default argument follows default argument
- 无论有多少默认参数,默认参数都不能在位置参数之前。
- 无论有多少默认参数,若不传入默认参数值,则使用默认值。
- 若要更改某一个默认参数值,又不想传入其他默认参数,且该默认参数的位置不是第一个,则可以通过参数名更改要更改默认参数值。
- 若有一个默认参数通过传入参数名更改参数值,则其他想要更改的默认参数都需要传入参数名更改数值,否则报错。
- 更改默认参数值时,传入默认参数的顺序不需要根据定义的函数中的默认参数的顺序传入,不过最好同时传入参数名,否则容易出现执行结果与预期不一致的情况。
>>> def personinfo(name,age=20,addr = 'xian'):
... print("姓名:",name,"\t年龄:",age,"\t地址:",addr)
... return
...
>>> personinfo('zth') # 传入必须参数
姓名: zth 年龄: 20 地址: xian
>>>
>>>
>>>
>>> personinfo('zth',21) # 传入必须参数,更改第一个默认参数值
姓名: zth 年龄: 21 地址: xian
>>>
>>>
>>>
>>> personinfo('zth',21,'chengdu') # 传入必须参数,更改全部默认参数值
姓名: zth 年龄: 21 地址: chengdu
>>>
>>>
>>>
>>> personinfo('zth',addr='chengdu') # 传入必须参数,更改指定默认参数名的参数值
姓名: zth 年龄: 20 地址: chengdu
>>>
>>>
>>> personinfo('zth',addr='chengdu',age = 21) # 传入必须参数,指定参数名并更改值
姓名: zth 年龄: 21 地址: chengdu
>>>
>>>
>>> personinfo('zth',21,addr='chengdu') # 传入必须参数,第一个默认参数不带参数名,第二个带
姓名: zth 年龄: 21 地址: chengdu
>>>
>>>
>>> personinfo('zth',age = 21,addr='chengdu') # 传入必须参数,两个默认参数都带参数名
姓名: zth 年龄: 21 地址: chengdu
>>>
>>>
>>> personinfo('zth',age = 21,'chengdu') # 传入必须参数,第一个默认参数带参数名,第二个不带
File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument
【注】定义默认参数要牢记一点:默认参数必须指向不变对象
>>> def add_end( L = []):
... L.append('zth')
... return L
...
>>>
>>> #正常调用时,结果没有错误
...
>>>
>>> add_end([1,2,3])
[1, 2, 3, 'zth']
>>> add_end(['fbb',20])
['fbb', 20, 'zth']
>>>
>>>
>>> add_end() # 使用默认参数调用,一开始没有错误
['zth']
>>> add_end() # 再次调用将会出错
['zth', 'zth']
>>>
>>>
>>> # 修改,用None这个不变对象来实现
...
>>> def add_end( L = None):
... if L is None:
... L= []
... L.append('zth')
... return L
...
>>>
>>> add_end()
['zth']
>>> add_end()
['zth']
原因解释如下:
Python函数在定义的时候,默认参数 L
的值就被计算出来了,即[]
,因为默认参数L
也是一个变量,它指向对象[]
,每次调用该函数,如果改变了 L
的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]
了。
为什么要设计str
、None
这样的不变对象呢?因为不变对象一旦创建,对象内部的数据就不能修改,这样就减少了由于修改数据导致的错误。此外,由于对象不变,多任务环境下同时读取对象不需要加锁,同时读一点问题都没有。我们在编写程序时,如果可以设计一个不变对象,那就尽量设计成不变对象。
4. 可变参数
可变参数也被称为不定长参数,顾名思义,就是传入的参数个数是可变的,可以是任意个(0、1、2 … N)。
要定义可变参数,仅需在参数名之前添加一个星号(*
)。在函数内部,这些参数被包装为一个 tuple。
必须在函数定义中使用位置参数以及可变参数时,位置参数始终必须在可变参数之前。
语法格式:
def functionname([formal_args,] *var_args_tuple ):
"函数_文档字符串"
function_suite
return [expression]
加了星号 * 的参数会以元组(tuple)的形式导入,存放所有未命名的变量参数。
如果在函数调用时没有指定参数,它就是一个空元组。我们也可以不向函数传递未命名的变量。
>>> def printinfo( arg1,*vartuple):
... "打印传入的所有参数"
... print(arg1)
... for var in vartuple:
... print(var)
... return
...
>>>
>>>
>>> printinfo(10) # 可变参数未指定参数
10
>>>
>>>
>>> printinfo(10,20,3,5) # 可变参数指定多个参数
10
20
3
5
参数带两个星号 **基本语法如下:
def functionname([formal_args,] **var_args_dict ):
"函数_文档字符串"
function_suite
return [expression]
加了两个星号 ** 的参数会以字典的形式导入。
>>> def printinfo( arg1,**vardict):
... "打印传入的所有参数"
... print(arg1)
... print(vardict)
... return
...
>>>
>>> printinfo(1,2,3,4)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: printinfo() takes 1 positional argument but 4 were given
>>> printinfo(1, a=2,b=3)
1
{'a': 2, 'b': 3}
声明函数时,参数中星号 * 可以单独出现,例如:
如果单独出现星号 * 后的参数必须用关键字传入。
>>> def f(a,b,*,c):
... return a+b+c
...
>>>
>>> f(1,2,3) # 报错
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: f() takes 2 positional arguments but 3 were given
>>>
>>>
>>> f(1,2,c=3) # 正常
6
5. 组合参数
在 Python 中定义函数可以用位置参数、关键字参数、默认值参数和可变关键字参数,这 4 种参数可以组合使用。
定义参数的顺序必须是:位置参数、默认参数、可变参数、命名关键字参数和关键字参数。
>>> def f1(a, b, c=0, *args, **kw):
... print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
...
>>>
>>> f1(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}
>>> f1(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}
>>> f1(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
>>> f1(1, 2, 3, 'a', 'b', x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
>>>
>>>
>>> def f2(a, b, c=0, *, d, **kw):
... print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
...
>>> f2(1, 2, d=99, ext=None)
a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}
四. 对参数进行解包
正如“可变参数”那样,也可在函数调用中使用 *
操作符。只不过在这种情况下,与在函数定义中 *
的语义相反,参数将被解包而不是打包。
>>> def greet(name, age):
... print(name, age)
...
>>>
>>> person = ('zth', 20)
>>>
>>> greet( *person)
zth 20
同样地,字典也可以用 **
操作符传递关键字参数:
>>>
>>> def greet(name, age=18):
... print(name, age)
... return
...
>>> person ={'name':'zth', 'age':20}
>>>
>>>
>>> greet( **person)
zth 20