原标题:Python函数参数详解,三天让你掌握python,不再是小白
引言
随着程序功能的增多,代码量随之增大,此时仍不加区分地把所有功能的实现代码放到一起,将会使得程序的组织结构不清晰,可读性变差,且程序中需要频繁使用同一功能时,只能重复编写该功能的实现代码,日积月累,程序将变得冗长,并且当某一功能需要修改时,又不得不找出所有定义及使用这段功能的地方修改之,管理维护的难度极大。
到底该如何解决提出的这些问题呢?我们完全可以从现实生活中找到简化程序设计的方案:比如一个修理工会事先准备好螺丝刀、锤子等工具,这样在进行修理的过程中,需要用到拧螺丝的功能时就直接拿来螺丝刀使用,需要用到锤击物体的功能时就直接拿来锤子使用,而无需临时制造。这个例子的核心在于事先准备好工具,遇到应用场景时’拿来就用。
在程序中,具备某一功能的‘工具指的就是函数,事先准备工具’的过程即函数的定义,拿来就用’即函数的调用。
一、函数的定义
函数的使用必须遵循’先定义,后调用’的原则。函数的定义就相当于事先将函数体代码保存起来,然后将内存地址赋值给函数名,函数名就是对这段代码的引用,这和变量的定义是相似的。没有事先定义函数而直接调用,就相当于在引用一个不存在的’变量名’。
1.1 什么是函数
函数就相当于具备某一功能的工具
函数的使用必须遵循一个原则:
先定义
后调用
1.2 为何要用函数
为了解决代码以下问题:
组织结构不清晰,可读性差
代码冗余
可维护性、扩展性差
1.3. 如何用函数
先定义(三种定义方式(无参函数,有参函数,空函数))
后调用
三种调用方式
1. 语句的形式:只加括号调用函数 add(1,2)
2、表达式形式:# 赋值表达式 # res=add(1,2)
# 数学表达式 res=add(1,2)*10
3、函数调用可以当做参数
res=add(add(1,2),10)
二、函数定义,调用 返回值
2.1 定义函数的基本语法
def 函数名(参数1,参数2,...):
"""文档描述"""
函数体
return 值
def: 定义函数的关键字;
函数名:函数名指向函数内存地址,是对函数体代码的引用。函数的命名应该反映出函数的功能;
括号:括号内定义参数,参数是可有可无的,且无需指定参数的类型;
冒号:括号后要加冒号,然后在下一行开始缩进编写函数体的代码;
“”“文档描述”"": 描述函数功能,参数介绍等信息的文档,非必要,但是建议加上,从而增强函数的可读性;
函数体:由语句和表达式组成;
return 值:定义函数的返回值,return是可有可无的。
若函数是工厂,参数则为工厂的原材料,返回值相当于工厂的产品
2.2 定义函数和调用函数内存变化
# 定义函数发生的事情
# 1、申请内存空间保存函数体代码
# 2、将上述内存地址绑定函数名
# 3、定义函数不会执行函数体代码,但是会检测函数体语法
# 调用函数发生的事情
# 1、通过函数名找到函数的内存地址
# 2、然后加括号就是在触发函数体代码的执行
2.2.1 有参函数
参数是函数的调用者向函数体传值的媒介,若函数体代码逻辑依赖外部传来的参数时则需要定义为参函数
def my_min(x,y):
res=x if x < y else y
return res
2.2.2 无参函数
def interactive():
user=input('user>>: ').strip()
pwd=input('password>>: ').strip()
return (user,pwd)
2.2.3 空函数
函数体为pass代表什么都不做,称之为空函数。定义空函数通常是有用的,因为在程序设计的开始,往往是先想好程序都需要完成什么功能,然后把所有功能都列举出来用pass充当函数体“占位符”,这将使得程序的体系结构立见,清晰且可读性强。例如要编写一个ftp程序,我们可能想到的功能有用户认证,下载,上传,浏览,切换目录等功能,可以先做出如下定义:
def auth_user():
"""user authentication function"""
pass
def download_file():
"""download file function"""
pass
def upload_file():
"""upload file function"""
pass
def ls():
"""list contents function"""
pass
def cd():
"""change directory"""
pass
之后我们便可以统筹安排编程任务,有选择性的去实现上述功能来替换掉pass,从而提高开发效率。
2.3 函数返回值
函数的使用分为定义阶段与调用阶段,定义函数时只检测语法,不执行函数体代码,函数名加括号即函数调用,只有调用函数时才会执行函数体代码
若需要将函数体代码执行的结果返回给调用者,则需要用到return。return后无值或直接省略return,则默认返回None,return的返回值无类型限制,且可以将多个返回值放到一个元组内 ,return多个返回值也是 以tuple的形式返回
>>> def test(x,y,z):
... return x,y,z #等同于return (x,y,z)
>>> res=test(1,2,3)
>>> print(res)
(1, 2, 3)
return是一个函数结束的标志,函数内可以有多个return,但只执行一次函数就结束了,并把return后定义的值作为本次调用的结果返回。
三、函数的参数
函数的参数分为形式参数和实际参数,简称形参和实参:
形参即在定义函数时,括号内声明的参数。形参本质就是一个变量名,用来接收外部传来的值。
形参与实参的关系:
在调用阶段,实参(变量值)会绑定给形参(变量名)
这种绑定关系只能在函数体内使用
实参与形参的绑定关系在函数调用时生效,函数调用结束后解除绑定关系
实参即在调用函数时,括号内传入的值,值可以是常量、变量、表达式或三者的组合:
#1:实参是常量
res=my_min(1,2)
#2:实参是变量
a=1
b=2
res=my_min(a,b)
#3:实参是表达式
res=my_min(10*2,10*my_min(3,4))
#4:实参可以是常量、变量、表达式的任意组合
a=2
my_min(1,a,10*my_min(3,4))
在调用有参函数时,实参(值)才会赋值给形参(变量名)。在Python中,变量名与值只是单纯的绑定关系,而对于函数来说,这种绑定关系只在函数调用时生效,在调用结束后解除。
3.1 形参与实参的具体使用
3.1.1 位置参数(包括位置形参和位置实参)
位置即顺序,位置参数指的是按顺序定义的参数,需要从两个角度去看:
在定义函数时,按照从左到右的顺序依次定义形参,称为位置形参,凡是按照这种形式定义的形参都必须被传值
def register(name,age,sex): #定义位置形参:name,age,sex,三者都必须被传值
print('Name:%s Age:%s Sex:%s' %(name,age,sex))
register() #TypeError:缺少3个位置参数
在调用函数时,按照从左到右的顺序依次定义实参,称为位置实参,凡是按照这种形式定义的实参会按照从左到右的顺序与形参一一对应
3.1.2关键字参数(实参)
在调用函数时,实参可以是key=value的形式,称为关键字参数,凡是按照这种形式定义的实参,可以完全不按照从左到右的顺序定义,但仍能为指定的形参赋值
register(sex='male',name='lili',age=18)
Name:lili Age:18 Sex:male
需要注意在调用函数时,实参也可以是按位置或按关键字的混合使用,但必须保证关键字参数在位置参数后面,且不可以对一个形参重复赋值
>>> register('lili',sex='male',age=18) #正确使用
>>> register(name='lili',18,sex='male') #SyntaxError:关键字参数name=‘lili’在位置参数18之前
>>> register('lili',sex='male',age=18,name='jack') #TypeError:形参name被重复赋值
3.1.3 默认值参数(形参)
在定义函数时,就已经为形参赋值,这类形参称之为默认参数,当函数有多个参数时,需要将值经常改变的参数定义成位置参数,而将值改变较少的参数定义成默认参数。例如编写一个注册学生信息的函数,如果大多数学生的性别都为男,那完全可以将形参sex定义成默认参数
def register(name,age,sex='male'): #默认sex的值为male
print('Name:%s Age:%s Sex:%s' %(name,age,sex))
定义时就已经为参数sex赋值,意味着调用时可以不对sex赋值,这降低了函数调用的复杂度
>>> register('tom',17) #大多数情况,无需为sex传值,默认为male
Name:tom Age:17 Sex:male
>>> register('Lili',18,'female') #少数情况,可以为sex传值female
Name:Lili Age:18 Sex:female
需要注意:
默认参数必须在位置形参之后
默认形参的值仅在函数定义阶段被赋值一次 ,且被赋予的是值的内存地址
>>> x=1
>>> def foo(arg=x):
... print(arg)
>>> x=5 #定义阶段arg已被赋值为1,此处的修改与默认参数arg无任何关系
>>> foo()
1
虽然默认值参数可以被指定任意类型,但是通常应设为不可变类型
def foo(n,arg=[]):
arg.append(n)
return arg
foo(1)
[1]
foo(2)
[1, 2]
foo(3)
[1, 2, 3]
每次调用是在上一次的基础上向同一列表增加值,修改如下
def foo(n,arg=None):
if arg is None:
arg=[] # 在内部改变
arg.append(n)
return arg
foo(1)
[1]
foo(2)
[2]
foo(3)
[3]
四、 可变长度的参数(*与**的用法)
参数的长度可变指的是在调用函数时,实参的个数可以不固定,而在调用函数时,实参的定义无非是按位置或者按关键字两种形式,这就要求形参提供两种解决方案来分别处理两种形式的可变长度的参数
4.1 可变长度的位置参数
如果在最后一个形参名前加*号,那么在调用函数时,溢出的位置实参,都会被接收,以元组的形式保存下来赋值给该形参
>>> def foo(x,y,z=1,*args): #在最后一个形参名args前加*号
... print(x)
... print(y)
... print(z)
... print(args) # 元组
>>> foo(1,2,3,4,5,6,7) #实参1、2、3按位置为形参x、y、z赋值,多余的位置实参4、5、6、7都被*接收,
以元组的形式保存下来,赋值给args,即args=(4, 5, 6,7)
1
2
3
(4, 5, 6, 7)
如果我们事先生成了一个列表,仍然是可以传值给args的 。*可以用在实参中,实参中带*,先将*后的值( 即可迭代对象)打散(拆解)成位置实参
>>> def foo(x,y,*args):
... print(x)
... print(y)
... print(args)
>>> L=[3,4,5] # L可以是任意可迭代对象,列表,元组,字符串,字典
>>> foo(1,2,*L) # *L就相当于位置参数3,4,5, foo(1,2,*L)就等同于foo(1,2,3,4,5)
1
2
(3, 4, 5)
>>> l='hello' # 字符串
>>> foo(1,2,L)
1
2
('h', 'e', 'l', 'l', 'o')
注意:如果在传入L时没有加*,那L就只是一个普通的位置参数了
>>> foo(1,2,L) #仅多出一个位置实参L
1
2
([1, 2, 3],)
如果形参为常规的参数(位置或默认),实参仍可以是*的形式
>>> def foo(x,y,z=3):
... print(x)
... print(y)
... print(z)
>>> foo(*[1,2]) #等同于foo(1,2)
1
2
3
形参与实参中都带*
def func(x,y,*args):
print(x,y,args)
func(1,2,[3,4,5,6]) # args=([3,4,5,6],)
func(1,2,*[3,4,5,6]) # args=(3,4,5,6)
func(*'hello') # func('h','e','l','l','o') args=('l','l','o')
如果我们想要求多个值的和,*args就派上用场了
>>> def add(*args):
... res=sum(args)
... return res
>>> add(1,2,3,4,5)
15
4.2 可变长度的关键字参数
如果在最后一个形参名前加**号,那么在调用函数时,溢出关键字参数,都会被接的收,以字典的形式保存下来赋值给该形参
>>> def foo(x,**kwargs): #在最后一个参数kwargs前加**
... print(x)
... print(kwargs)
>>> foo(y=2,x=1,z=3) #溢出的关键字实参y=2,z=3都被**接收,以字典的形式保存下来,赋值给kwargs
1
{'z': 3, 'y': 2}
如果我们事先生成了一个字典,仍然是可以传值给kwargs的 . **可以用在实参中(后跟的只能是字典),实参中带 **,先把 **后的值打散成关键字实参
>>> def foo(x,y,**kwargs):
... print(x)
... print(y)
... print(kwargs)
>>> dic={'a':1,'b':2}
>>> foo(1,2,**dic) #**dic就相当于关键字参数a=1,b=2,foo(1,2,**dic)等同foo(1,2,a=1,b=2)
1
2
{'a': 1, 'b': 2}
注意:如果在传入dic时没有加**,那dic就只是一个普通的位置参数了
>>> foo(1,2,dic) #TypeError:函数foo只需要2个位置参数,但是传了3个
1
如果形参为常规参数(位置或默认),实参仍可以是**的形式
>>> def foo(x,y,z=3):
... print(x)
... print(y)
... print(z)
>>> foo(**{'x':1,'y':2}) #等同于foo(y=2,x=1)
1
2
3
形参与实参中都带**
def func(x,y,**kwargs):
print(x,y,kwargs)
func(y=222,x=111,a=333,b=444) # kwargs= {'a': 333, 'b': 444}
func(**{'y':222,'x':111,'a':333,'b':4444}) # kwargs= {'a': 333, 'b': 4444}
4.3 组合使用* 与**
综上所述所有参数可任意组合使用,但定义顺序必须是:位置参数、默认参数,*args、命名关键字参数、 ** kwargs
可变参数*args与关键字参数**kwargs通常是组合在一起使用的,如果一个函数的形参为*args与**kwargs,那么代表该函数可以接收任何形式、任意长度的参数,在该函数内部还可以把接收到的参数传给另外一个函数(这在之后装饰器的实现中大有用处)
>>> def func(x,y,z):
... print(x,y,z)
>>> def wrapper(*args,**kwargs):
... func(*args,**kwargs)
>>> wrapper(1,z=3,y=2)
1 2 3
解释下述形式,即函数wrapper的参数特点是什么
def index(x,y,z):
print(x,y,z)
def wrapper(*args,**kwargs): # args=(1,) kwargs={'y':2,'z':3} # 形参中含有*,** 会将其包装
index(*args,**kwargs)
# index(*(1,),**{'y':2,'z':3}) # 实参中含有*,**,会将其打散
# index(1,z=3,y=2)
wrapper(1,z=3,y=2)
按照上述写法,在为函数wrapper传参时,其实遵循的是函数index的参数规则,调用函数wrapper的过程分析如下:
位置实参1被接收,以元组的形式保存下来,赋值给args,即args=(1,),关键字实参z=3,y=2被**接收,以字典的形式保存下来,赋值给kwargs,即kwargs={‘y’: 2, ‘z’: 3}
执行index(args,kwargs), 即index((1,),* {'y': 2, 'z': 3}),等同于func(1,z=3,y=2)
提示: *args、**kwargs中的args和kwargs被替换成其他名字并无语法错误,但使用args、kwargs是约定俗成的。
注意:python中所有值的传递,传递的都是不是值本身,而是值的引用,即内存地址
4.4. 命名关键字参数(了解)
# 命名关键字参数:在定义函数时,*后定义的参数,如下所示,称之为命名关键字参数
# 特点:
# 1、命名关键字实参必须按照key=value的形式为其传值
# def func(x,y,*,a,b): # 其中,a和b称之为命名关键字参数
# print(x,y)
# print(a,b)
#
# # func(1,2,b=222,a=111)
#
# 示例
# def func(x,y,*,a=11111,b):
# print(x,y)
# print(a,b)
#
# func(1,2,b=22222)
4.5 各种形参的组合使用
形参混用的顺序:位置形参,默认值形参,*args,命名关键字形参,**kwargs
# def func(x,y=111,*args,z,**kwargs):
# print(x) # 1
# print(y) # 1
# print(args) #(2, 4)
# print(z) # 5
# print(kwargs) # {'name': 'xioawang'}
func(1,*[1,2,4],z = 5,**{'name':'xioawang'})
# 实参混用的顺序:
# def func(x,y,z,a,b,c):
# print(x)
# print(y)
# print(z)
# print(a)
# print(b)
# print(c)
# func(111,y=222,*[333,444],**{'b':555,'c':666}) # 会报错
# func(111,y=222,333,444,b=555,c=666)
# func(111,*[333,444],a=222,**{'b':555,'c':666}) # 正确
# func(111,333,444,a=222,b=555,c=66)
# func(111,*[333,444],**{'b':555,'c':666},a=222,) # 正确
#func(111,3333,4444,b=555,c=666,a=222)返回搜狐,查看更多
责任编辑: