Python学习笔记系列之011:函数
导读:
1.函数的定义
2.函数的文档注释
3.函数的参数与调用
4.函数参数的类型(重点)
5.函数的返回值
6.函数的嵌套调用
7.局部变量和全局变量
所谓函数,就是把 具有独立功能的代码块 组织为一个小模块,在需要的时候 调用。
函数的使用包含两个步骤:
1.定义函数 —— 封装 独立的功能
2.调用函数 —— 享受 封装 的成果
函数的作用:在开发程序时,使用函数可以提高编写的效率以及代码的 重用 。
函数的特点:功能性;隐藏细节;避免编写重复的代码
一、函数的定义
定义函数使用关键字 def ,后接函数名和放在圆括号( )中的可选参数列表,函数内容以冒号起始并且缩进。一般格式如下:
def 函数名(参数列表):
"""文档字符串"""
函数体
return [expression]
注意:参数列表可选,文档字符串可选,return语句可选。
示例:
def fib(n): """Print a Fibonacci series""" a, b = 0, 1 while b < n: print(b, end=' ') a, b = b, a+b print() fib(2000) # call f = fib # assignment f(2000)
函数名的值是一种用户自定义的函数类型。函数名的值可以被赋予另一个名字,使其也能作为函数使用。
1.函数名称 应该能够表达 函数封装代码 的功能,方便后续的调用
2.函数名称 的命名应该 符合 标识符的命名规则
二、函数的文档注释
函数体的第一个语句可以是三引号括起来的字符串, 这个字符串就是函数的文档字符串,或称为docstring 。我们可以使用 print(function.__doc__) 输出文档:
def fun():
"""Some information of this function.
This is documentation string."""
return
print(fun.__doc__)
文档字符串主要用于描述一些关于函数的信息,让用户交互地浏览和输出。建议养成在代码中添加文档字符串的好习惯。
在 函数调用 位置,使用help(函数名)快捷键 CTRL + Q 可以查看函数的说明信息。
注意:
因为 函数体相对比较独立,函数定义的上方 ,应该和其他代码(包括注释)保留两个空行。
小技巧:
在给函数添加注释时,可以鼠标光标放在 函数名 上,pycharm上会出现一个小灯,选择 ”insert documentation string stub" 会自动添加注释模板。
三、函数的参数与调用
3.1 参数的使用
在函数名的后面的小括号内部填写 参数;多个参数之间使用 , 分隔。
def sum_2_num(num1, num2): result = num1 + num2 print("%d + %d = %d" % (num1, num2, result)) sum_2_num(50, 20)
3.2 参数的作用
函数的参数,增加函数的 通用性,针对 相同的数据处理逻辑,能够 适应更多的数据 在函数内部,把参数当做 变量 使用,进行需要的数据处理。
函数调用时,按照函数定义的参数顺序,把 希望在函数内部处理的数据,通过参数传递。
3.3 形参和实参
形参:定义函数时,小括号中的参数,是用来接收参数用的,在函数内部作为变量使用。
实参:调用函数时,小括号中的参数,是用来把数据传递到函数内部用的。
3.4 函数的调用
通过 函数名() 即可完成对函数的调用。
# 只有在调用函数时,之前定义的函数才会被执行。
# 函数执行完成之后,会重新回到之前的程序中,继续执行后续的代码。
# 调用时,实参的个数和先后顺序应该和定义函数中要求的一致。
注意:
不能将 函数调用 放在 函数定义 的上方!
因为在 使用函数名 调用函数之前,必须要保证Python 已经知道函数的存在,否则控制台会提示 NameError: name 'say_hello' is not defined (名称错误:say_hello 这个名字没有被定义)
四、函数参数的类型
4.1 必须参数
def add(x, y): result = x + y return result c = add(2, 3) print(c) # 5
4.2 缺省参数(默认参数)
在形参中默认有值的参数,称之为缺省参数。
def add(x, y = 3, z=4): result = x + y +z return result c = add(2) #c = add(x = 2) print(c) # 9
默认参数必须放在必须参数之后。
def add(x, y = 3, z=4): result = x + y +z return result c = add(2, 5) print(c) # 11
调用时如果要改变默认参数的值,最好,使用关键字参数。
def add(x, y = 3, z=4): result = x + y +z return result c = add(2, z= 10) #c = add(x = 2) print(c) # 15
关键字参数必须放在其他参数之后。
def add(x, y = 3, z=4, w=5): result = x + y +z return result c = add(2, y= 6, 4, w=7) #c = add(x = 2) print(c) # 报错
注意:通常情况下默认值只被计算一次,但如果默认值是一个可变对象时会有所不同, 如列表, 字典, 或 大多类的对象时。例如,下面的函数在随后的调用中会累积参数值:
def fun(a, L=[]): L.append(a) print(L) fun(1) # 输出[1] fun(2) # 输出[1, 2] fun(3) # 输出[1, 2, 3]
默认参数必须指向不变对象:
先定义一个函数,传入一个 list,添加一个 END 再返回:
def add_end(L=[]): L.append('END') return L
当你正常调用时,结果似乎不错:
>>> add_end([1, 2, 3]) [1, 2, 3, 'END'] >>> add_end(['x', 'y', 'z']) ['x', 'y', 'z', 'END']
当你使用默认参数调用时,一开始结果也是对的:
>>> add_end() ['END']
但是,再次调用 add_end()时,结果就不对了:
>>> add_end() ['END', 'END'] >>> add_end() ['END', 'END', 'END']
很多初学者很疑惑,默认参数是[],但是函数似乎每次都“记住了”上次添加了'END'后的 list。
原因解释如下:
Python 函数在定义的时候,默认参数 L 的值就被计算出来了,即[],因为默认参数 L 也是一个变量,它指向对象[],每次调用该函数,如果改变了 L 的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。
所以,定义默认参数要牢记一点:默认参数必须指向不变对象!
要修改上面的例子,我们可以用 None 这个不变对象来实现:
def add_end(L=None): if L is None: L = [] L.append('END') return L
现在,无论调用多少次,都不会有问题:
>>> add_end() ['END'] >>> add_end() ['END']
为什么要设计 str、 None 这样的不变对象呢?因为不变对象一旦创建,对象内部的数据就不能修改,这样就减少了由于修改数据导致的错误。
此外,由于对象不变,多任务环境下同时读取对象不需要加锁,同时读一点问题都没有。我们在编写程序时,如果可以设计一个不变对象,那就尽量设计成不变对象。
4.3 关键字参数
函数也可以通过 keyword=value 形式的关键字参数来调用,因为明确指出了对应关系,所以参数的顺序也就无关紧要了。
使用关键字参数,可以无视参数顺序。
def add(x, y): result = x + y return result c = add(y = 3, x = 2) print(c) # 5
4.4 可变参数(不定长参数)
有时可能需要一个函数能处理比当初声明时更多的参数, 这些参数叫做不定长参数,声明时不会命名。
通过在形参前加一个星号(*)或两个星号(**)来指定函数可以接收任意数量的实参。
基本语法如下:
def functionname([formal_args,] *args, **kwargs):
"""函数_文档字符串"""
function_suite
return [expression]
def fun(*args): print(type(args)) print(args) fun(1,2,3,4,5,6) # 输出: # <class 'tuple'> # (1, 2, 3, 4, 5, 6) def fun(**args): print(type(args)) print(args) fun(a=1,b=2,c=3,d=4,e=5) # 输出: # <class 'dict'> # {'d': 4, 'e': 5, 'b': 2, 'c': 3, 'a': 1}
从两个示例的输出可以看出:
当参数形如 *args 时,会存放所有未命名的变量参数,传递给函数的任意个实参会按位置被包装进一个元组(tuple);
当参数形如 **kwargs 时,会存放命名参数,传递给函数的任意个 key=value 实参会被包装进一个字典(dict)。
>>> def fun(a, b, *args, **kwargs): ... """可变参数演示示例""" ... print "a =", a ... print "b =", b ... print "args =", args ... print "kwargs: " ... for key, value in kwargs.items(): ... print key, "=", value ... >>> fun(1, 2, 3, 4, 5, m=6, n=7, p=8) # 注意传递的参数对应 a = 1 b = 2 args = (3, 4, 5) kwargs: p = 8 m = 6 n = 7 >>> c = (3, 4, 5) >>> d = {"m":6, "n":7, "p":8} >>> fun(1, 2, *c, **d) # 注意元组与字典的传参方式 # 结果同上 >>> fun(1, 2, c, d) # 注意不加星号与上面的区别 a = 1 b = 2 args = ((3, 4, 5), {'p': 8, 'm': 6, 'n': 7}) kwargs:
如果很多个值都是不定长参数,那么这种情况下,可以将缺省参数放到*args的后面,但如果有**kwargs的话,**kwargs必须是最后的。
def sum_nums_3(a, *args, b=22, c=33, **kwargs): print(a) print(b) print(c) print(args) print(kwargs) sum_nums_3(100, 200, 300, 400, 500, 600, 700, b=1, c=2, mm=800, nn=900) # 100 # 1 # 2 # (200, 300, 400, 500, 600, 700) # {'mm': 800, 'nn': 900}
说明:
如果很多个值都是不定长参数,那么这种情况下,可以将缺省参数放到 *args的后面, 但如果有**kwargs的话,**kwargs必须是最后的。
4.5 解包参数
上一点说到传递任意数量的实参时会将它们打包进一个元组或字典,当然有打包也就有解包(unpacking)。通过 单星号和双星号对List、Tuple和Dictionary进行解包:
def fun(a=1, b=2, c=3): print(a+b+c) fun() # 正常调用 list1 = [11, 22, 33] dict1 = {'a':40, 'b':50, 'c':60} fun(*list1) # 解包列表 fun(**dict1) # 解包字典 # 输出: # 6 # 66 # 150
注:*用于解包Sequence,**用于解包字典。需要拆的数据的个数要与变量的个数相同,否则程序会异常。解包字典会得到一系列的 key=value ,故本质上就是使用关键字参数调用函数。
解包:
d = 1, 2, 3 print(type(d)) # <class 'tuple'> a, b, c = d print(a) # 1 print(b) # 2 print(c) # 3
4.6 命名关键字参数
对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过 kw 检查。
仍以 person()函数为例,我们希望检查是否有 city 和 job 参数:
def person(name, age, **kw): if 'city' in kw: # 有 city 参数 pass if 'job' in kw: # 有 job 参数 pass print('name:', name, 'age:', age, 'other:', kw) 但是调用者仍可以传入不受限制的关键字参数: >>> person('Jack', 24, city='Beijing', addr='Chaoyang', zipcode=123456)
如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收 city 和 job 作为关键字参数。这种方式定义的函数如下:
def person(name, age, *, city, job): print(name, age, city, job)
和关键字参数**kw 不同,命名关键字参数需要一个特殊分隔符*, *后面
的参数被视为命名关键字参数。
调用方式如下:
>>> person('Jack', 24, city='Beijing', job='Engineer') Jack 24 Beijing Engineer
命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错:
>>> person('Jack', 24, 'Beijing', 'Engineer') Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: person() takes 2 positional arguments but 4 were given
由于调用时缺少参数名 city 和 job, Python 解释器把这 4 个参数均视为位置参数,但 person()函数仅接受 2 个位置参数。
命名关键字参数可以有缺省值,从而简化调用:
def person(name, age, *, city='Beijing', job): print(name, age, city, job)
由于命名关键字参数 city 具有默认值,调用时,可不传入 city 参数:
>>> person('Jack', 24, job='Engineer') Jack 24 Beijing Engineer
使用命名关键字参数时,要特别注意, *不是参数,而是特殊分隔符。
如果缺少*, Python 解释器将无法识别位置参数和命名关键字参数:
def person(name, age, city, job): # 缺少 *, city 和 job 被视为位置参数 pass
可变参数无法和命名关键字参数混合。
五、函数的返回值
在程序开发中,有时候,会希望一个函数执行结束后,告诉调用者一个结果,以便调用者针对具体的结果做后续的处理。
返回值是函数完成工作后,最后给调用者的一个结果。在函数中使用 return 关键字可以返回结果。
调用函数一方,可以使用变量来接收函数的返回结果。
return后面可以是元组,列表、字典等,只要是能够存储多个数据的类型,就可以一次性返回多个数据。
如果return后面有多个数据,那么默认是元组。
如果函数没有返回值,仍然为变量赋值,则变量的值为None。
注意:return 表示返回,后续的代码都不会被执行。
def sum_2_num(num1, num2): """对两个数字的求和""" return num1 + num2 # 调用函数,并使用result变量接收计算结果 result = sum_2_num(10, 20) print("计算结果是 %d" % result)
在python中我们可不可以返回多个值?
>>> def divid(a, b): ... shang = a//b ... yushu = a%b ... return shang, yushu ... >>> sh, yu = divid(5, 2) >>> sh 5 >>> yu 1
本质是利用了元组。
六、函数的嵌套调用
一个函数里面 又调用 了 另外一个函数,这就是 函数嵌套调用。
如果函数test2中,调用了另外一个函数test1那么执行到调用 test1 函数时,会先把函数test1 中的任务都执行完,才会回到 test2 中调用函数 test1 的位置,继续执行后续的代码。
def test1(): print("*" * 20) def test2(): print("-" * 20) test1() print("-" * 20) test2() 结果: -------------------- ******************** --------------------
6.1 打印分隔线案例
需求 1
定义一个函数能够打印 任意重复次数 的分隔线。
def print_line(char, times): print(char * times)
需求 2
定义一个函数能够打印 5行 的分隔线,分隔线要求符合需求1。
提示:工作中针对需求的变化,应该冷静思考, 不要轻易修改之前已经完成的,能够正常执行的函数!
def print_line(char, times): print(char * times) def print_lines(char, times): row = 0 while row < 5: print_line(char, times) row += 1
6.2 求3个数的平均值
需求1
求3个数的和
def sum3Number(a,b,c): return a+b+c # return 的后面可以是数值,也可是一个表达式
需求2
完成对3个数求平均值
def average3Number(a,b,c): # 因为sum3Number函数已经完成了3个数的就和,所以只需调用即可 # 即把接收到的3个数,当做实参传递即可 sumResult = sum3Number(a,b,c) aveResult = sumResult/3.0 return aveResult # 调用函数,完成对3个数求平均值 result = average3Number(11,2,55) print("average is %d"%result)
七、局部变量和全局变量
局部变量:
局部变量,就是在函数内部定义的变量。
不同的函数,可以定义相同的名字的局部变量,但是各用个的不会产生影响。
局部变量的作用,为了临时保存数据需要在函数中定义变量来进行存储,这就是它的作用。
全局变量:
在函数外边定义的变量叫做全局变量。
全局变量能够在所有的函数中进行访问。
如果在函数中修改全局变量,那么就需要使用global进行声明,否则出错。
如果全局变量的名字和局部变量的名字相同,那么使用的是局部变量的,小技巧:强龙不压地头蛇。
在函数内部定义的变量拥有一个局部作用域,在函数外定义的拥有全局作用域。
注意:在函数内部可以引用全局变量,但无法对其赋值(除非用 global 进行声明)。
a = 5 #全局变量a def func1(): print('func1() print a=',a) def func2(): a = 21 # 局部变量a print('func2() print a =', a) def func3(): global a a = 10 # 修改全局变量a print('func3() print a =', a) func1() #5 func2() #21 func3() #10 print('the global a =', a) #10
可变类型的全局变量:
在函数中不使用global声明全局变量时不能修改全局变量的本质是不能修改全局变量的指向,即不能将全局变量指向新的数据。
对于不可变类型的全局变量来说,因其指向的数据不能修改,所以不使用global时无法修改全局变量。
对于可变类型的全局变量来说,因其指向的数据可以修改,所以不使用global时也可修改全局变量。
a = 1 def f(): a += 1 print(a) f() # 报错 li = [1,] def f2(): li.append(1) print (li) f2() # [1, 1] print(li) # [1, 1]