一、函数的定义
函数的使用必须遵循’先定义,后调用’的原则。函数的定义就相当于事先将函数体代码保存起来,然后将内存地址赋值给函数名,函数名就是对这段代码的引用,这和变量的定义是相似的。没有事先定义函数而直接调用,就相当于在引用一个不存在的’变量名’。
二、函数的语法结构:
def 函数名(参数1,参数2,...):
"""文档描述"""
函数体
return 值
- def: 定义函数的关键字;
- 函数名:函数名指向函数内存地址,是对函数体代码的引用。函数的命名应该见名知意的反映出函数的功能;
- 括号:括号内定义参数,参数是函数的调用者向函数体传值的媒介,若函数体代码逻辑依赖外部传来的参数时则需要定义为参函数,否则,则不需要定义参数,因此参数是可有可无的,且无需指定参数的类型,参数的讲解详见下文;
- 冒号:括号后要加冒号,然后在下一行开始缩进编写函数体的代码;
- """文档描述""": 描述函数功能,参数介绍等信息的文档,非必要,但是建议加上,从而增强函数的可读性;
- 函数体:由语句和表达式组成;
- return 值:定义函数的返回值,return是可有可无的。若需要将函数体代码执行的结果返回给调用者,则需要用到return。return后无值或直接省略return,则默认返回None,return的返回值无类型限制,且可以将多个返回值放到一个元组内。return是一个函数结束的标志,函数内可以有多个return,但只执行一次函数就结束了,并把return后定义的值作为本次调用的结果返回
三、函数的调用
函数的调用可以按照形式分为三种
1、语句形式:
foo() 直接调用被命名为foo函数,执行函数内部的代码块
2、表达式形式:
m=my_min(1,2) #执行函数的子代码块,将调用函数的返回值赋值给x
n=10*my_min(1,2) #执行函数的子代码块,将调用函数的返回值乘以10的结果赋值给n
3、函数调用作为参数的形式:
my_min(2,3)作为函数my_min的第二个参数,实现了取1,2,3中的较小者赋值给m
m=my_min(1,my_min(2,3))
四、函数的分类
在python中函数的分类如下:
1.内置函数:len() , strip() , splite() , append() 等
2.自定义函数:
2.1 有参函数,即定义函数的过程中定义了参数,关于参数的学习详见下文
2.2无参函数,此类函数不依靠外部输入的参数来获取函数所需要的数据,调用该类函数就可以执行被定义函数的子代码块
2.3空函数,空函数主要用于梳理结构,定义一个没有子代码块的函数,用pass补全语法结构即可
五、函数的参数
5.1参数的分类
按照函数的阶段分为形式参数和实际参数,所谓形参实参归根到底,形参就是在函数定义的阶段,设置的变量名,实参就是在函数调用阶段传入的数值,从而达到为当初定义函数时设置的参数赋值的目的
(1)形式参数
在函数定义阶段,括号内声明的参数,简称形参,实际相当于一个变量名,用来接收在函数调用阶段输入的值
(2)实际参数
在函数被调用阶段,在括号内输入的具体数据,简称实参,相当于变量的值,完成对形参的赋值操作,从而让函数在运行子代码块的过程中可以有具体的数值,括号内传入的值,值可以是常量、变量、表达式或三者的组合:
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中,变量名与值只是单纯的绑定关系,而对于函数来说,这种绑定关系只在函数调用时生效,在调用结束后解除。
5.2形参与实参的具体使用
5.2.1位置参数
位置即顺序,位置参数指的是按照位置顺序定义的参数,需要从两个方面去看待
(1)位置形参:在定义函数时,按照从左到右的顺序依次定义形参,称为位置形参,凡是按照这种形式定义的形参都必须被传值
def register(name,age,sex): #定义位置形参:name,age,sex,三者都必须被传值
... print('Name:%s Age:%s Sex:%s' %(name,age,sex))
...
>>> register() #TypeError:缺少3个位置参数
(2)位置实参:在函数调用阶段,按照从左到右的顺序定义的实参,称为位置实参,凡是按照这种形式定义的实参会按照从左到右的顺序与形参一一对应
>>> register('lili',18,'male') #对应关系为:name=’lili’,age=18,sex=’male’
Name:lili Age:18 Sex:male
5.2.2关键字参数
与位置参数不同,关键字参数针对的是实参,即在传值的时候,按照K:V的形式,指名道姓的为形参传值,因此在传值的时候可以不按照从左到右的顺序传值也能为形参赋值(人家都指名道姓了,还要啥自行车,还要啥顺序呀!!!!)但是,虽然不需要完全按照从左到右的顺序去传值,也不是想怎么传就可以怎么传的,还是需要遵循一定的规则:
1.若所有的实参都是以关键字参数的形似传入的,没有限制,保证每个参数都有对应的数值就行
2.关键字参数也可以和位置实参组合使用,但必须保证关键字参数在位置参数后面,(即参数按照位置的顺序从左往右传值,一旦决定使用关键字参数的形式为参数开始传值,那么后续的参数都必须使用关键字参数的方法传值,后续的这部分参数,不需要按照顺序,只要保证每个参数都有对应的值即可)且不可以对一个形参重复赋值
>>> 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被重复赋值
5.2.3默认参数
默认参数,区别于关键字参数的只针对实际参数,默认参数只针对形式参数而言,假如某一个参数,他的值在大多数情况下都是不变的,那么为了以后调用函数的时候简单,我们就可以在函数定义阶段,就为这个参数赋值,从而,在函数调用阶段,只需要在遇到少数情况下,为这个参数赋值即可,降低函数调用的复杂性
def register(name , age ,sex ='male')
>>> register('tom',17) #大多数情况,无需为sex传值,默认为male
Name:tom Age:17 Sex:male
>>> register('Lili',18,'female') #少数情况,可以为sex传值female
Name:Lili Age:18 Sex:female
规定如下:
1.默认参数必须在位置形参之后 原理同关键字参数,即默认参数之后在不能出现位置形参
2.默认参数的值只有在函数定义阶段被赋值一次,不能被重复赋值
x=1
>>> def foo(arg=x): #x存储的内存中数据1的存储地址,此处,将这个地址赋值给age
... print(arg)
...
>>> x=5 #定义阶段arg已被赋值为1,此处的修改与默认参数arg无任何关系
>>> foo()>>>>>>1 #x=5即将5这个数据的内存地址给x,然后跟前边的age在无关系
3.默认参数一般设置为不可变类型
(1)如果设置成可变类型的列表,每次调用是在上一次的基础上向同一列表增加值,与函数定义的初衷相悖
def foo(n,arg=[]):
... arg.append(n)
... return arg
...
>>> foo(1)
[1]
>>> foo(2)
[1, 2]
>>> foo(3)
[1, 2, 3]
(2)将默认参数改为不可变类型,修改如下
>>> def foo(n,arg=None):
... if arg is None:
... arg=[ ]
... arg.append(n)
... return arg
...
>>> foo(1)
[1]
>>> foo(2)
[2]
>>> foo(3)
[3]
综上所述:形参设置为默认参数的时候,一般要设置为不可变类型
可变类型:字典、列表、
不可变类型:数字、字符串、元组
5.2.4可变长度的参数(*号与**号的应用)
参数的长度可变指的是在调用函数时,实参的个数可以不固定,而在调用函数时,参数的定义无非是按位置的位置参数或者按关键字的关键字参数两种形式,这就要求形参提供两种解决方案来分别处理两种形式的可变长度的参数
1.可变长度的位置参数
如果在最后一个形惨前面加*,那么,在调用函数的时候,多余的实参,都会被加了*的形参打包接受,打包为一个元组赋值给*后边的变量名,所以实参无论是多少个值,函数都是可以被调用的
a.只有形参中加了*
例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)
例2:
如果我们想要求多个值的和,*args,这种可变长度的形参就派上用场了
>>> def add(*args): #调用的时候输入要求和的数字,因为只有一个形参,所以输入的实参是以元组的形式被变量接收
... res=0 # 定义一个新变量
... for i in args: # 利用for循环遍历输入被接收成元组的实参
... res+=i
... return res
...
>>> add(1,2,3,4,5)
15
b.形参中无可变长度的参数,实参中有*
例3:形参中无可变长度的参数,实参中依旧可以用*,其主要功能是打散列表
如果形参为常规的参数(位置或默认),实参仍可以是*的形式
>>> def foo(x,y,z=3):
... print(x)
... print(y)
... print(z)
...
>>> foo(*[1,2]) #等同于foo(1,2)
1
2
3
c.形参和实参定义和输入的时候都加*
例4:输入的实参中加*
如果我们事先生成了一个列表,仍然是可以传值给*args的
>>> def foo(x,y,*args):
... print(x)
... print(y)
... print(args)
...
>>> L=[3,4,5]
>>> foo(1,2,*L) # *L就相当于位置参数3,4,5, foo(1,2,*L)就等同于foo(1,2,3,4,5)
1
2
(3, 4, 5)
例5:为了更清楚的理解实参中*的作用,函数同例4,如果在传入L时没有加*,那L就只是一个普通的位置参数了
>>> L=[3,4,5]
>>> foo(1,2,L) #仅多出一个位置实参L
1
2
([3, 4, 5],)
注:*在实参中最重要的是可以将列表打散,使其成为一个个单独的元素,与下文的**号联系记忆
2.可变长度的关键字参数
如果理解了可变长度的位置参数,那么理解起可变长度的关键字参数就很容易了,因为二者原理一样,只有细微处有差异
例1:如果在最后一个形参名前加**号,那么在调用函数时,溢出的关键字参数,都会被接收,以字典的形式保存下来赋值给该形参
>>> 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}
例2:如果我们事先生成了一个字典,仍然是可以传值给**kwargs的 同可变长度的位置参数
注意:如果在传入dic时没有加**,那dic就只是一个普通的位置参数了
例3:如果形参为常规参数(位置或默认),实参仍可以是**的形式 **的作用同* 可以打散字典
>>> 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
3.命名关键字参数(了解)
命名关键字传参:在形参中会出现*的用法,想要限定函数的调用者必须以key=value的形式传值,Python3提供了专门的语法:需要在定义形参时,用*作为一个单独的分隔符号,*号之后的形参称为命名关键字参数。对于这类参数,在函数调用时,必须按照key=value的形式为其传值,且必须被传值,
a:如果出现在*号后面的形参,那么,实参传递的时候,必须以关键字的形式传递
例1:
def register(name,age,*,sex,height): #sex,height为命名关键字参数
pass
# register('kevin', 10, 'male', 1.9) 因为命名关键字参数没有按照关键字的形式传递,所以是错误的
register('kevin', 10, sex='male', height=1.9,)
b:命名关键字参数也可以有默认值,从而简化调用
例2:>>> def register(name,age,*,sex='male',height):
... print('Name:%s,Age:%s,Sex:%s,Height:%s' %(name,age,sex,height))
...
>>> register('lili',18,height='1.8m')
Name:lili,Age:18,Sex:male,Height:1.8m
需要强调的是:sex不是默认参数,height也不是位置参数,因为二者均*在后,所以都是命名关键字参数,形参sex=’male’属于命名关键字参数的默认值,因而即便是放到形参height之前也不会有问题。
c:如果形参中已经有一个在命名关键字前出现过的形参(例:age)了,在命名关键字参数就不再需要一个单独的*作为分隔符号了(*age即可)
例3:>>> def register(name,age,*args,sex='male',height):
... print('Name:%s,Age:%s,Args:%s,Sex:%s,Height:%s' %(name,age,args,sex,height))
...
>>> register('lili',18,1,2,3,height='1.8m') #sex与height仍为命名关键字参数
Name:lili,Age:18,Args:(1, 2, 3),Sex:male,Height:1.8m
5.3总结
综上所述所有参数可任意组合使用,但定义顺序必须是:位置参数、默认参数、*args(或*单独隔开)、命名关键字参数、**kwargs(可变长度的关键字参数)