为了将我们遇到的复杂问题简单化,我们引入了函数,举个例子来说,制造飞机是个复杂的系统工程,我们可以将制造飞机的工序分为发动机制造、机身制造、电子信息系统、生命支持系统等等不同的模块,将这些模块整体组装调试便制造出了飞机,当然,这说起来容易的事情,是多少代科研人员接续奋进的结果,函数,也如同制造飞机中的每个模块,我们将要实现的功能进行封装,具体函数里面怎么样实现我们不得而知,我们只关心函数提供什么样的服务,这便是函数的优势,而且,不同的程序或者过程都可以在需要的时候调用该函数,从而函数还实现了代码的复用,节约了编程的复杂度,提高了效率,那什么样的是函数,怎么样定义和使用函数呢?
1、函数的定义
1.1 函数的基本概念
我们先举个最简单的例子:
def abb(m,n): #定义一个函数abb
z = m + n #这里和向下一行是函数体内容
return z #用return返回一个结果
print(abb(1,2)) #调用abb函数
形式参数:1、我们先直观上简单看这个示例,首先用关键字def对函数进行定义,abb便是我们定义的函数名,后面的括号里面的内容是我们定义的参数,便于在调用时进行传值,是调用过程和函数体内部进行数据交换的通道,因为函数头这两个参数是用字母代替的,具体值是多少要看我们在调用函数时传递进来多少,因此,我们把函数头里面定义的这个参数,就是本示例中定义的m和n称为形式参数,简称为形参,作用就是占据这个位置,便于后续调用。我们也注意到了,形参是不需要定义数据类型的,我们是通过后面要讲到的实参自动来判断数据类型。
2、当然,如果我们没有任何值传入到函数体内,我们可以不定义m、n这种形参,但是(m,n)中的括号()是不能够省略的,即使没有参数,也需要加上括号,这是函数头的标准要求。
3、另外,括号后面的冒号:是不能够省略的,这是定义的函数头与下面的函数体之间的分界线。
函数体:在def定义函数后,我们就需要定义这个函数所要实现的功能,例如示例,定义了形参进行相加的功能,并通过return返回相加的数据,这便是函数体实现的功能,当然,在函数体中我们还可以调用其他函数或者后面要将要讲到的类,函数也可以调用自身,调用自身的过程称为递归,当然,递归函数我们必须设置一个跳出条件,不然无限制的递归就是一个死循环,后面我们还会讲到递归函数,递归函数在使用时或者说使用的场景又会有自己的一些特点特性。
return返回:在函数中,想返回一个数据需要使用return来进行返回,return后面的返回内容,可以加括号也可以不加括号,而return返回的值是什么类型,这取决于函数体内,对返回值的运算处理是什么数据类型而进行确定,当然这个过程是解释器自己完成的,不需要我们人工的干预,这也是python易学的一个重要原因,python都学到这里了,继续加油吧!当然,如果我们没有返回值,在函数中就直接定义了print(m,n),那么,我们就不需要return来返回数据了,我们就不需要return语句了。
函数的调用:示例中,我们定义了一个函数abb,在函数体外,我们用print打印出了我们调用的函数abb,这里顺便说一句,函数体内,严格按照空格来区分代码是否是函数体内的代码,一旦缩进出现问题,可能会出现结果不准或者报错等多种情况,当程序不报错但输出结果不正确时,我们有时候很难发现是哪里出现问题,因此,除了集成开发工具自动进行校准外,我们在日常写代码时也要做好相应的检查。
实参:函数调用中,abb(1,2)中的1,2便是我们所说的实参,表面意思就是实际参数的意思,1,2分别在函数中替代了定义时的m和n,将形式上的参数替换为实际使用的参数,这便是实参,实际参数的简称。
以上便是有一个小示例我们看到的函数结构,通过这个示例,我想我们就能够总结出来函数的定义结构了。
1.2 函数的定义语法
在python中,函数的定义结构语法为:
def 函数名([形参1,形参2,……]): #无形参,形参可省略
函数体内容
[return 返回值或返回内容] #无返回值或者内容,可省略
根据上面一部分的示例,我们就可以对函数定义有以下几点总结:
1、使用关键字def对函数进行定义;
2、函数可以没有形参,没有形参也需要保留括号,同时不需要对形参类型进行定义;
3、函数头以冒号结束,这个是不能省的;
4、函数体以缩进表示函数体的开始;
5、函数体内可以用空值等内容用于程序调试;
6、用关键字return返回相应的返回值或者内容,return不是必须存在的关键字,只有需要返回某个数据时才使用,同时也不需要指定返回值的类型;
7、函数名不能够与关键字重复,同时也避免用仅区分大小写字母形式的关键字,必要时,建议函数名用其实现功能的英文或者中文缩写来命名。
2、参数的类型
刚才说到,定义的函数中可以使用形参也可适不适用形参,根据函数索要实现的功能来进行定义,那么,如果我们定义了形参,实参和形参之间是如何进行传递而使函数能够正确实现其功能的呢?
2.1 位置参数
位置参数,顾名思义肯定和参数所处的位置有关,如果一个函数定义后,我们传递实参时,完全按照定义形参的顺序进行数值传递,并且实参的数量和形参的数量相同,这便是位置参数。
如上面提到过得abb函数示例,我们看到,调用函数abb时候,位置参数m=1,n=2,实现了函数的调用。
def abb(m,n): #定义abb函数,形参为m和n
z = m + n
return(z)
print(abb(1,2)) #引用函数abb,1和2就是分别赋值给了m和n
但是如果我在abb调用时多加一个位置参数,如下面的代码:
def abb(m,n):
z = m + n
return(z)
print(abb(1,2,3)) #增加一个位置参数3
就会出现下面的报错信息:
print(abb(1,2,3))
^^^^^^^^^^
TypeError: abb() takes 2 positional arguments but 3 were given
因此,位置参数,顾名思义位置和数量是一一对应的。
2.2 默认值参数
在定义函数时,如果我们将某个数据在定义函数时就赋值给相应的形参,那这就是默认值参数,在调用默认值参数时,可以不用给默认值参数赋值,则函数会直接调用定义时赋值的数值,当然,我们也可以在调用时给默认值参数赋值,赋值后将改变本次调用时的调用数值,在下一次调用时,仍是定义函数时的默认值,不会因为赋值改变而定义时的数据有改变。
def abb(m,n=3): #定义函数abb时,我们将n赋值为默认值3
z = m + n
return(z)
print(abb(1)) #调用函数abb时,我们进需要赋值m,n默认为定义是的数值3
结果为:
4
如果我们将默认值参数赋值,将得到不同的结果:
def abb(m,n=3): #定义函数abb时,n赋值默认值3
z = m + n
return(z)
print(abb(1,10)) #调用函数abb时,将默认值参数赋值为10
结果为:
11
需要特别注意的是,使用默认值参数进行定义时,任何一个默认值参数右侧都不能再出现没有默认值参数的普通位置参数,否则就会出现提示语法错误。
我们看下面的例子:
def abb(m=1,n): #我们先定义了m=1,右侧有普通的位置参数
z = m + n
return(z)
print(abb(1,10)) #虽然我们在调用参数时,对相应的参数都进行了赋值,但仍然会报错
我们看下面的错误信息:
def abb(m=1,n):
^
SyntaxError: non-default argument follows default argument
2.3 关键参数
如果我们想灵活使用函数中的参数,不想收到位置、默认值等方面的束缚,那我们应该怎么样来定义参数和使用参数呢,因此,我们引入了关键参数。
关键参数主要通过参数名字进行传值,明确指出哪个值传给哪个参数,实参顺序完全可以和形参位置顺序不一致,也完全不会影响最后得到的传递值计算结果,避免了使用人需要牢记参数位置、顺序等方面的麻烦,使得函数的调用和参数的传递更加灵活方面,在参数较多较复杂的场景下,提高了编程效率。
我们看下面的例子:
def abb(m,n): #定义函数abb,引入形参m和n
z = m + n
return(z)
print(abb(n=1,m=10)) #使用关键字参数赋值n=1,m=10
2.4 可变长度参数
如果我们调用函数时,对传入的实参数量不确定,因此没法对形参进行定义,数量、位置都不确定,因此引入了可变长度参数,可变长度参数的定义方法有两种形式,分别为*parameter和**parameter,*parameter用来接收任意多个位置参数,并将接受到的参数放在一个元组中,**parameter用于接收多个关键参数,并将接收到的关键参数存放在字典中。我们来看下面的示例:
def demo(*p): #定义函数demo,定义可变长度参数*p
print(p)
demo(1,2,3,4,5,6,7,8,9,10) #接收到位置参数,并将数据存放在p中
结果为:
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
那接收关键参数是什么情况呢?我们看下面的示例:
def demo(**p): #定义函数demo,并定义接收关键参数的可变长度参数
for item in p.items(): #for循环对接收的数据提取
print(item)
demo(m=1,n=2,k=5,q=3) #接收关键参数
结果为:
('m', 1)
('n', 2)
('k', 5)
('q', 3)
2.5 序列解包
与可变长度参数相反,对序列的解包是对实参来讲的,如果传递来的实参是相应的数据结构,我们需要将他们分解出来,然后传给相应的参数,对实参的序列解包,同样有*和**两种形式,也就是说,调用含有多个位置参数的函数时,我们可以使用元组、列表、集合、字典以及其他可以迭代的对象作为实参,并在实参前加*或者**,python将对传递来的序列进行解包,然后把相应的值分别传给相应的形参变量。
我们看下面的例子:
>>> def abb(m,n,q,k):
print(m+n+q+k)
>>> a1 = (1,2,3,4)
>>> abb(*a1) #解包元组
10
>>> a2 = [4,5,6,7]
>>> abb(*a2) #解包列表
22
>>> a3 = {5:'abc',6:'def',7:'glk',8:'poq'}
>>> abb(*a3) #解包字典的键
26
>>> abb(*a3.values()) #解包字典的值
abcdefglkpoq
>>> a4 = {1,2,3,4}
>>> abb(*a4) #解包集合
10
如果实参是个字典,可以使用**对其进行解包,会把字典转换成类似于关键参数的形式进行传递。对于这种类型的解包,要求传递来的实参,也就是这个字典,所有的键必须和函数的形参名称或者与函数中**的可变长度参数相一致。
就像下面的示例:
>>> def p(a,b,c=3): #定义函数p
print(a,b,c)
>>> q = {'a':5, 'b':6, 'c':7}
>>> f(**q) #解包后,实参替换形参
5 6 7
>>> def f(a=10,b=11,c=12):
print(a,b,c)
>>> f(**q) #解包后,实参替换形参
5 6 7
>>> def f(**p): #接收字典形式的可变长度参数
for item in p.items():
print(item)
>>> p = {'m':22,'n':33,'k':55}
>>> f(**p) #对字典进行解包
('m', 22)
('n', 33)
('k', 55)
下一节,我们将学习变量作用域、lambda函数、内置函数。