10. 函数
一、什么是函数
函数也是对象,对象是内存中专门用来存储数据的一块区域。函数可以用来保存一些可执行代码的,并且可以在需要时,对这些语句进行多次调用。
二、创建函数
创建函数也称为定义函数。我们可以使用 def 关键字来定义函数,它的语法格式如下:
def 函数名(参数列表):
# 函数体
pass
- 函数名:按照命名规则起个名字,在 Python 中建议小写加下划线的方式;
- 函数体:整个函数真正要执行的代码,一般函数体前面缩进缩进四个空格(缩进统一即可);
- 参数列表:专门用来存储调用时传递给函数的数据;
def say_hello():
print("This is a function!")
print("hello world!")
定义好的函数并不会执行,你需要调用它,它才能执行里面的代码;
三、函数的调用
函数的调用也就是执行函数。调用函数的语法格式如下:
函数名(参数列表)
say_hello()
同一个函数只需要定义一次,但是可以调用多次;
四、函数的参数
4.1、形参与实参
在定义函数时,可以在函数名后的 () 中定义数量不等的 形参,多个使用之间使用逗号 , 隔开。形参也称 形式参数,定义形参就相当于在函数内部声明了变量,但是并不赋值。如果函数定义时,指定了形参,那么在调用函数传入的值称为 实际参数,简称 形参。
在调用阶段,实参会绑定给对应的形参,简单来说,有几个形参就要传几个实参。这种绑定关系只能在函数体内使用。实参和形参的绑定关系在函数调用时生效,函数调用结束后解除绑定关系。
# 定义函数时,指定形参
def sum(a, b):
print(a, ' + ', b, ' = ', (a + b))
# 调用函数时,传递实参
sum(10,20)
如果实参的个数与形参的个数不匹配会报以下的错误:
TypeError: sum() takes 2 positional arguments but 3 were given
TypeError: sum() missing 1 required positional argument: 'b'
4.2、参数传递的方式
实参的传递方式有 2 种,一种是 位置参数,另一种是 关键字参数。
位置参数 就是对应位置的实参赋值给对应位置的形参。即第一个实参赋值给第一个形参,第二个实参赋值给第二个实参,以此类推。
def showInfo(name, age, gender):
print("name: ", name, ", age: ", age, ", gender: ", gender)
showInfo("Sakura", 10, '女')
关键字参数 可以不按照形参定义的顺序去传递,而直接根据参数名去传递参数。
def showInfo(name, age, gender):
print("name: ", name, ", age: ", age, ", gender: ", gender)
showInfo(name = "Sakura", gender = '女', age = 10)
按照关键字传值的时候,如果形参名不存在,会报以下的错误:
TypeError: showInfo() got an unexpected keyword argument 'sex'
位置参数可以和关键字参数混合使用。
def showInfo(name = "unknown", age = 0, gender = "secrecy"):
print("name: ", name, ", age: ",age, ", gender: ", gender)
showInfo("Sakura",gender = '女', age = 10)
混合使用位置参数和关键字参数时,必须将位置参数写在前面。否则会报以下错误:
SyntaxError: positional argument follows keyword argument
在函数调用时,我们不能通过位置参数和关键字参数同时给一个形参赋多次值,否则会报以下错误:
TypeError: showInfo() got multiple values for argument 'name'
4.3、默认参数
定义形参时,我们可以为形参指定默认值。指定默认值 后,如果用户传递参数,则默认值没有任何作用;如果用户没有传递参数,则默认值就会生效。
def showInfo(name = "unknown", age = 0):
print("name: ", name, ", age: ",age)
showInfo("Sakura")
位置形参必须在默认形参的左边,否则会报以下错误:
SyntaxError: non-default argument follows default argument
虽然形参的默认值可以指定为任意的数据类型,但是不推荐使用 可变类型;
names = ["Sakura","Mikoto"]
def printName(name=names):
for name in names:
print(name,end='\t')
print()
printName()
names.append("Shana")
printName()
4.4、不定长参数
不定长参数(可变参数)指的是在调用函数时,传入的值(实参)的个数不确定。在定义函数时,我们可以在形参前面加上一个 * ,这样这个形参将会获得所有的实参。它会将所有的实参保存到一个元组中。
# *nums会接受所有的位置参数,并且会将这些参数统一保存到一个元组中
def sum(*nums):
print("nums = ", nums, "type: ", type(nums))
result = 0
for n in nums:
result += n
print(result,'\n')
sum()
sum(1,2)
sum(1,2,3)
在 * 号的形参只能有一个,带 * 号的参数也可以和其它参数配合使用。
# 第一个参数给num1,第二个参数给num2,剩下的都保存到nums的元组中
def sum(num1, num2, *nums):
print("num1: ", num1)
print("num2: ", num2)
print("nums: ", nums, "type: ", type(nums))
result = 0
for n in nums:
result += n
print(result, '\n')
sum(1,2)
sum(1,2,3)
sum(1,2,3,4)
sum(1,2,3,4,5)
可变参数可以不写在最后,但是,带 * 后的所有参数,必须以关键字参数的形式传递。
# 第一个位置参数给num1,剩下的位置参数给nums的元组,num2必须使用关键字参数
def sum(num1, *nums, num2):
print("num1: ", num1)
print("num2: ", num2)
print("nums: ", nums, "type: ", type(nums),'\n')
sum(1,2,num2 = 3)
sum(1,2,3,num2 = 4)
sum(1,2,3,4,num2 = 5)
# 所有的位置参数后给nums,num1和num2必须使用关键字参数
def sum(*nums, num1, num2):
print("num1: ", num1)
print("num2: ", num2)
print("nums: ", nums, "type: ", type(nums),'\n')
sum(1,num1 =2,num2 =3)
sum(1,2,num2 = 3,num1 = 4)
sum(1,2,3,num1 = 4,num2 = 5)
如果在形参的开头直接写一个 *,则要求我们的所有参数必须以关键字参数的形式传递。
def sum(*, num1=10, num2=20, num3):
print("num1: ", num1)
print("num2: ", num2)
print("num3: ", num3)
print()
sum(num3=30)
sum(num1=11, num2=22,num3=33)
sum(num2=10,num3=10)
* 形参只能接受位置参数,而不能接受关键字参数。但我们可以使用 ** 形参接受其它的关键字参数,它会将这些参数统一保存在字典中,字典的 key 就是参数的名字,字典的 value 就是参数的值。
def sum(**nums):
print("nums: ", nums, ", type: ", type(nums))
sum(num1 =1,num2 = 2, num3 = 3)
** 形参只能有一个,并且必须写在所有参数的最后。
def sum(num1, num2, **nums):
print("num1: ", num1)
print("num2: ", num2)
print("nums: ", nums, ", type: ", type(nums))
sum(num1 =1,num2 = 2, num3 = 3, num4 = 4)
4.5、参数的解包
传递实参时,也可以在序列类型的参数前添加 * 号,这样它会自动将序列中的元素一次作为参数传递。这里要求序列中元素的个数必须和形参中的个数一致。
def fun(num1, num2, num3):
print("num1: ", num1)
print("num2: ", num2)
print("num3: ", num3)
t = (10,20,30)
fun(*t)
print()
l = [10,20,30]
fun(*l)
我们还可以通过 ** 对字典进行解包。字典的 key 要与函数的形参对应。
def fun(num1, num2, num3):
print("num1: ", num1)
print("num2: ", num2)
print("num3: ", num3)
d = {"num1":10, "num2":20, "num3":30}
fun(**d)
4.6、实参的类型
函数在调用时,解析器不会检查实参的类型,实参可以是任意类型的对象。
def showInfo(arg):
print("arg: ", arg)
showInfo("Sakura")
showInfo(True)
showInfo(123)
showInfo(None)
showInfo([1,2,3])
在函数中对形参进行重新赋值,不会影响到其它的变量。
def fun(arg):
arg = 30
print("arg: ", arg, "id: ",id(arg))
c = 10
fun(c)
print('c: ', c, "id: ", id(c))
但是,如果形参指向的是一个对象,当我们通过形参去修改对象时,会影响到所有指向该对象的变量。
def fun(arg):
arg[0] = 30
print('arg: ', arg, 'id: ',id(arg))
c = [1,2,3]
fun(c)
print('c: ', c, 'id: ', id(c))
此时,我们可以传递实参的一个副本。
def fun(arg):
arg[0] = 30
print("arg: ", arg, "id: ",id(arg))
c = [1,2,3]
# fun(c.copy())
fun(c[:])
print('c: ', c, "id: ", id(c))
函数的参数默认传递的是 形参的引用;
五、函数的返回值
返回值 就是函数执行以后返回的结果。我们可以通过 return 来指定函数的返回值。我们可以直接使用函数的返回值,也可以通过一个变量来接受函数的返回值。return 后面跟什么值,函数就会返回什么值。return 后面可以跟任意对象,返回值甚至可以是一个函数。
def sum(*nums):
print("nums = ", nums, "type: ", type(nums))
result = 0
for n in nums:
result += n
print(result,'\n')
sum()
sum(1,2)
sum(1,2,3)
如果仅仅写一个 return 或不写 return,则相当于 return None。
def fun():
pass
print(fun())
def fun():
return
print(fun())
在函数中,return 后的代码都不会执行。return 一旦执行,函数自动结束。
def fun():
print("hello")
return
print("world")
print(fun())
我们还可以使用都好分割开多个返回值,此时会被 return 返回为元组的形式。
def add(a,b):
return a,b,a+b
result = add(10,20)
print(result)
print(type(result))
函数名 是函数对象,打印函数名实际是在打印函数对象;
函数名() 是在调用函数,打印函数名()实际上是在打印函数的返回值;
return 是函数结束的标志,即函数体代码一旦运行到 return 会立刻终止函数的运行,并且会将 return 后的指当作本次运行的结果返回。
六、文档字符串
我们可以通过 help() 函数查询 python 中函数的用法。help() 函数是 python 内置的一个函数,它的使用语法格式如下:help(函数对象)。
help(print)
在定义函数时,我们可以在函数内部编写文档字符串,文档字符串 就是函数的说明。当我们编写了文档字符串时,就可以通过 help() 函数来查看函数的说明。文档字符串其实就是直接在函数的第一行写一个字符串,一般我们会使用三重引号,这样可以写多行。
def sum(num1:int=0, num2:int=0,*nums:int):
"""
这是一个加法函数
@param num1 接收第一个加数
@param num2 接收第二个加数
@param *nums 可以接受任意多个参数
@return result 该函数返回相加的结果
"""
result = 0
for n in nums:
result += n
help(sum)
七、作用域
作用域指的是变量生效的区域。在 python 中一共有 2 中作用域。一种是 全局作用域,另一种是 函数作用域。
全局作用域 在程序执行时创建,在程序结束时销毁。所有函数以外的区域都是全局作用域。在全局作用域中定义的变量,都属于全局变量,全局变量可以在程序的任意位置被访问。
函数作用域 在函数调用时创建,在调用结束时销毁。函数每调用一次,就会产生一个新的函数作用域。在函数中作用域中定义的变量,都是局部变量,它只能在函数内部被访问。
b = 20
def fun():
# a定义在函数内部,所以它的作用域就是函数内部,函数外部无法访问
a = 10
print("函数内部:a = ", a)
print("函数内部:b = ", b)
fun()
print("函数外部:b = ", b)
当我们使用一个变量时,会优先在当前作用域中寻找变量。如果有则使用,如果没有则继续去上一级作用域中去寻找。如果上一级作用域中有则使用,如果依然没有则继续去上级作用域中寻找,以此类推。
def fun1():
a = 10
b = 10
print("fun1中的a:", a)
print("fun1中的b:", b)
def fun2():
a = 20
print("fun2中的a:", a)
print("fun2中的b:", b)
fun2()
fun1()
如果直至找到全局作用域,依然也没有找到,则会抛出异常:
NameError: name 'c' is not defined
在函数中为变量赋值时,默认都是为局部变量赋值。如果希望在函数内部修改全局变量,则需要使用 global 关键字来声明变量。
a = 10
b = 10
def fun():
# 在函数中为变量赋值时,默认都是为局部变量赋值
a = 20
# 如果希望在函数内部修改全局变量,则需要使用 global 关键字来声明变量
global b
# 修改全局变量
b = 30
print("函数内部:a = ", a)
print("函数内部:b = ", b)
fun()
print("函数外部:a = ", a)
print("函数外部:b = ", b)
建议全局变量大写,局部变量小写;
八、类型提示
在 Python 3.5 以上的版本函数支持类型提示功能。
def 函数名(形参:形参提示性信息) -> 返回值提示信息:
...
def showInfo(name:str,age:int=0) -> dict:
print(f"name:{name},age:{age}")
return {"name":name,"age":age}
result = showInfo("Sakura",10)
print(result)
result = showInfo("Sakura")
print(result)
# 可以通过__annotations__查看函数的提示信息
print(showInfo.__annotations__)
九、函数递归
递归简单理解就是自己引用自己。递归式函数就是在函数中自动调用自己(直接或间接调用函数本身)。递归是解决问题的一种方式,它和循环很像。它的整体思想是,将一个大的问题分解为一个个的小问题,直到问题无法分解时,再去解决问题。
递归式函数的两个要件:
- 基线条件
- 问题可以被分解为的最小问题,当满足基线条件时,递归就不在执行了
- 递归条件
- 将问题继续分解的条件
def factorial(num):
# 基线条件,判断num是否为1,如果为1则此时不能再继续递归
if num == 1:
return 1
return num * factorial(num-1)
ressult = factorial(10)
print(ressult)
十、内建函数
Python 解释器运行时,会自动加载很多的常用的变量、函数以及类,这些就是内建功能或者数据,我们把自动导入的函数称之为 内建函数。我们可以通过 dir(__builtin__)
来查看 Python 解释器运行时自动加载的数据。
【1】、range() 函数
range(start, stop[, step])
range() 可以用来生成一个自然数的序列,它有三个参数,第一个参数是 起始位置,可以省略,默认是 0,第二个参数是 结束位置,第三个参数是 步长,也可以省略,默认是 1。
range() 函数返回一个 range 对象,是一个可迭代的对象,可以配合 for 或 next() 等使用。如果想要得到列表,可通过 list() 函数。
a = range(5)
print(a)
print(type(a))
print(list(a))
【2】、map() 函数
map(function, iterable, *iterables)
map() 函数会根据提供 function 指向的函数对指定的序列 iterable 做映射。参数序列中的每一个元素分别调用 function() 函数,将每次 function() 函数的返回值,存放到序列中当作最后的结果。
a = map(lambda x: x**2, range(1,6))
print(a)
print(type(a))
for item in a:
print(item)
a = map(lambda x, y: x+y, [1, 2, 3], (4, 5, 6))
print(a)
print(type(a))
for item in a:
print(item)
【3】、filter() 函数
filter(function, iterable)
filter() 函数会对序列 iterable 中的每个元素调用 function 指向的函数执行过滤操作。在每次执行时,结果为 True 的序列元素存储到结果中。
a = filter(lambda x : x%2, range(1,6))
print(a)
print(type(a))
for item in a:
print(item)
十一、函数对象
在 Python 中,函数是一等对象,它具有以下特点:① 对象是在运行时创建的; ② 能赋值给变量或作为数据结构中的元素; ③ 能作为参数传递; ④ 能作为返回值返回;
def func():
print("func()函数执行了")
# 可以将函数赋值给一个变量
f = func
print(f)
print(func)
# 通过变量调用函数
f()
def func():
print("func()函数执行了")
# 可以把函数当作函数的参数传入
def fun(f):
print(f)
f()
fun(func)
def func():
print("func()函数执行了")
# 可以把函数当作函数的返回值返回
def fun():
return func
res = fun()
print(res)
res()
十二、高阶函数
高阶函数至少要符合以下两个特点中的一个:① 接收一个或多个函数作为参数; ② 将函数作为返回值返回;
当我们使用一个函数作为参数时,实际上是将指定的代码传递进了目标函数。
def filter(list, fun):
new_list = []
for n in list:
if fun(n):
new_list.append(n)
return new_list
def IsEven(num):
return num %2 == 0
def IsGreater5(num):
return num > 5
list = [x for x in range(11)]
new_list = filter(list,IsEven)
print(new_list)
new_list = filter(list,IsGreater5)
print(new_list)
十三、匿名函数
匿名函数也称为 lambda 函数表达式,lambda 表达式专门用于创建一些简单的函数,它是函数创建的又一种方式。它的语法格式如下:
lambda 参数列表 : 返回值
我们可以通过如下方法调用 lambda 函数表达式:
(lambda 形参列表 : 返回值)(实参列表)
print(lambda a, b : a + b)
print((lambda a, b : a + b)(10, 20))
我们也可以将一个匿名函数赋值给变量。
def filter(list, fun):
new_list = []
for n in list:
if fun(n):
new_list.append(n)
return new_list
list = [x for x in range(11)]
new_list = filter(list, lambda num : num % 2 == 0)
print(new_list)
new_list = filter(list, lambda num : num > 5)
print(new_list)
匿名函数一般都是作为参数使用;
如果匿名函数不需要形参,可以书写如下格式:lambda : 表达式
匿名函数默认有 return;