什么是函数:
函数就是执行特定任务以完成特定功能的一段代码,它接受输入(参数),最后生成输出(参数)。其中输入、输出都是可选的。可以在程序中将某段代码定义成函数,并制定一个函数名及接受输入参数,这样,就可以在程序的其他地方通过函数名多次调用并执行该段代码,每次调用并执行后,都会根据接收的输入参数,执行特定任务后生成相应的输出。
为什么需要函数:
- 复用代码:如果程序中需要多次完成某个特定的功能,我们可以将该功能定义成函数,然后在其他地方调用即可,无需多次编写
- 隐藏实现的细节:我们无需关注函数实现的细节,只需关注其输入参数及输出参数即可
- 提高可维护性:把完成某个特定功能的代码定义为函数后,如果需要对这段代码进行修改,只需要在一个地方修改即可
- 提高可读性,便于调试。
定义函数
定义函数的语法格式:
def 函数名([形式参数1, 形式参数2, ....]):
函数体
每个函数都有一个函数名,用于函数的调用。函数名必须遵守标识符的命名规则。
形式参数简称形参,用于在调用函数时接收输入,也就是接收传递的实际参数,简称实参。形参是可选的,其作用域仅限于函数体
函数体是用于执行特定任务以完成特定功能的主体代码,其代码块必须缩进。如果有返回值,可以通过语句 return xxx 进行返回,同时结束函数体的执行。如果函数不需要输出,可以在函数体内部通过语句 return 直接结束函数体的执行,或者让函数体正常执行结束,其实,函数在这两种情况下都是有返回值的,其返回值是 None。函数体在调用的时候才会被执行,因此,定义函数不会改变程序的执行流程
调用函数
调用函数时,每个实参都被用于初始化相应的形参。所有形参都被初始化之后,函数体对应的代码块被执行。程序的执行流程会跳转到定义函数体内,执行函数体对应的代码块,执行完函数体后再跳回到调用函数的地方,继续执行下一条语句
def decide_args(arg1, arg2):
if arg1 and arg2:
return arg1, arg2
elif (not arg1) and (not arg2):
return
else:
result = arg1 or arg2
return result
# 函数对象
print(decide_args) # <function decide_args at 0x000002445013F4C8>
# 函数类型
print(type(decide_args)) # <class 'function'>
print(decide_args(18, 'Hello')) # (18, 'Hello')
print(decide_args([], '')) # None
print(decide_args({}, 18)) # 18
函数的调用之位置实参
调用函数时,可以根据每个形参所在形参中的位置传递对应位置的实参,从而用每个实参初始化对应位置的形参,这样的实参称为位置实参
def f(a, b, c):
print('a = ', a, 'b = ', b, 'c = ', c)
f(2, 5, 8) # a = 2 b = 5 c = 8
f(5, 8, 2) # a = 5 b = 8 c = 2
f(8, 5, 2) # a = 8 b = 5 c = 2
函数的调用之关键字实参
在调用函数时,传递的实参的形式可以为:形参名 = 实参值,从而用指定的实参值初始化指定名称的形参,这样的实参称为关键字实参。
def f(a, b, c):
print('a = ', a, 'b = ', b, 'c = ', c)
# 由于关键字实参中指定了形参名,所以实参和形参的匹配关系更加清晰,而且每个关键字实参的位置可以是任意的
f(a=2, b=5, c=8) # a = 2 b = 5 c = 8
f(b=5, c=8, a=2) # a = 2 b = 5 c = 8
f(c=8, b=5, a=2) # a = 2 b = 5 c = 8
# 调用函数时,可以组合使用位置实参和关键字实参,但是位置实参必须位于关键字实参之前。
f(2, 5, c=8) # a = 2 b = 5 c = 8
函数的调用之实参的传递
调用函数时把实参传递给形参,从而用实参初始化形参,本质上执行了赋值语句:形参 = 实参,相当于给实参对象贴了一个标签,标签名就是形参名
如果实参是可变类型,在函数体内对形参的任何修改其实就是对实参对象的修改。
def f(arg1, arg2):
print('初始化形参后:arg1 = ', arg1, 'arg2 = ', arg2)
arg1 = arg1 * 2
arg2.append(4)
print('修改形参后:arg1 = ', arg1, 'arg2 = ', arg2)
i = 10
L = [1, 2, 3]
print('调用函数前:i = ', i, 'L = ', L) # 调用函数前:i = 10 L = [1, 2, 3]
f(i, L)
# 初始化形参后:arg1 = 10 arg2 = [1, 2, 3]
# 修改形参后:arg1 = 20 arg2 = [1, 2, 3, 4]
print('调用函数后:i = ', i, 'L = ', L) # 调用函数后:i = 10 L = [1, 2, 3, 4]
print('###########################################################')
i = 10
L = [1, 2, 3]
print('调用函数前:i = ', i, 'L = ', L) # 调用函数前:i = 10 L = [1, 2, 3]
f(i, L[:])
# 初始化形参后:arg1 = 10 arg2 = [1, 2, 3]
# 修改形参后:arg1 = 20 arg2 = [1, 2, 3, 4]
print('调用函数后:i = ', i, 'L = ', L) # 调用函数后:i = 10 L = [1, 2, 3]
函数的定义之多个返回值
返回值组成元组
def classify_numbers(numbers):
"""列表中的所有数分为奇数偶数两类"""
odds = []
evens = []
for number in numbers:
if number % 2:
odds.append(number)
else:
evens.append(number)
return odds, evens
result = classify_numbers([15, 86, 39, 26, 53, 68])
print(result) # ([15, 39, 53], [86, 26, 68])
def lookup_min_max(numbers):
"""查找列表中的最小值和最大值"""
if len(numbers) == 0:
return
min_num = numbers[0]
max_num = numbers[0]
for number in numbers[1:len(numbers)]:
if number < min_num:
min_num = number
elif number > max_num:
max_num = number
return min_num, max_num
result = lookup_min_max([35, 26, 19, 86, 93, 68])
print(result) # (19, 93)
函数定义之带默认值的形参
定义函数时,可以给形参设置默认值,这样调用函数时,如果不传递对应的实参,就会使用设置的默认值初始化形参
给形参设置默认值之后,可以简化函数的调用,只有与默认值不符的形参,才需要传递额外的实参
在定义函数时,没有设置默认值的形参,必须位于设置了默认值的形参之前,否则无法根据位置来匹配位置实参所对应的形参
def f1(a, b=5):
print('a = ', a, 'b = ', b)
f1(2, 6) # a = 2 b = 6
f1(2) # a = 2 b = 5
def f2(a, b=5, c=8):
print('a = ', a, 'b = ', b, 'c = ', c)
f2(2, 6, 9) # a = 2 b = 6 c = 9
f2(2) # a = 2 b = 5 c = 8
f2(2, 6) # a = 2 b = 6 c = 8
f2(2, c=9) # a = 2 b = 5 c = 9
# 给形参设置默认值之后,调用函数就存在多种调用方式
def fun(a, b=5):
print('a = ', a, 'b = ', b)
fun(3) # a = 3 b = 5
fun(a=3) # a = 3 b = 5
fun(3, 6) # a = 3 b = 6
fun(a=3, b=6) # a = 3 b = 6
fun(b=6, a=3) # a = 3 b = 6
fun(3, b=6) # a = 3 b = 6
# 定义函数时,给形参设置的默认值就被计算出来了。因此,如果给形参设置的默认值是可变类型的对象,
# 并且前一次调用时,在函数体内修改了形参的默认值,那么修改后的值将作为下一次调用函数时形参的默认值。
def fun1(L=[]):
L.append(18)
print(L)
fun1() # [18]
fun1() # [18, 18]
fun1() # [18, 18, 18]
函数的定义之使用 * 定义关键字形参
定义函数时,可以在所有形参的某个位置,添加一个 * ,这样,* 后面的所有形参都被定义为只能接收关键字实参的关键字形参
def f(a, b, *, c, d):
print('a = ', a, 'b = ', b, 'c = ', c, 'd = ', d)
f(1, 2, c=3, d=4) # a = 1 b = 2 c = 3 d = 4
# f(1, 2, 3, 4) # TypeError: f() takes 2 positional arguments but 4 were given
函数的定义之使用 * 定义个数可变的位置形参
定义函数时,可能无法事先确定传递的位置实参的个数,在这种情况下,可以在形参前添加一个 * ,将形参定义为个数可变的位置形参,从而可以接收 0 个或任意多个位置实参。这些位置实参会将个数可变的位置形参初始化一个元组。
定义函数时,最多只能定义一个,个数可变的位置形参
通常,把个数可变的位置形参定义为最后一个形参,以便接收所有剩余的位置实参。如果个数可变的位置形参不是最后一个形参,那么其后面的所有形参都被定义为只能接收关键字实参的关键字形参
def f(*args):
print(args)
f() # ()
f(1) # (1,)
f(1, 2, 3) # (1, 2, 3)
# 以下不建议
def fun2(a, *b, c, d):
print('a = ', a, 'b = ', b, 'c = ', c, 'd = ', d)
fun2(1, 2, 3, 4, c=5, d=6) # a = 1 b = (2, 3, 4) c = 5 d = 6
# fun2(1, 2, 3, 4, 5, 6) # TypeError: fun2() missing 2 required keyword-only arguments: 'c' and 'd'
函数的调用之使用 * 将序列中的每个元素都转换为位置实参
在调用函数的时候,可以在序列的前面添加一个 * ,从而将序列中的每个元素都转换为一个单独的位置实参
def f(a, b, c):
print('a = ', a, 'b = ', b, 'c = ', c)
f(1, 2, 3) # a = 1 b = 2 c = 3
L = [1, 2, 3]
# 列表 L 整体作为一个位置实参
# f(L) # TypeError: f() missing 2 required positional arguments: 'b' and 'c'
f(L[0], L[1], L[2]) # a = 1 b = 2 c = 3
f(*L) # a = 1 b = 2 c = 3
def fun(*args):
print(args)
# 列表 L 整体作为一个位置实参
fun(L) # ([1, 2, 3],)
# 现将序列中的每个元素都转换为一个单独的位置实参,再用这些位置实参将个数可变的位置形参,初始化为一个元组
fun(*L) # (1, 2, 3)
函数的定义之使用 ** 定义个数可变的关键字形参
定义函数时,可能无法事先确定传递的关键字实参的个数,在这种情况下,可以在形参前添加两个 * ,将形参定义为个数可变的关键字形参,从而可以接收 0 个或任意多个关键字实参。这些关键字实参会将个数可变的关键字形参初始化一个字典。
定义函数时,最多只能定义一个,个数可变的关键字形参
因为调用函数时,位置实参必须位于关键字实参之前,所以个数可变的位置形参也必须位于个数可变的关键字形参之前
def f(**kwargs):
print(kwargs)
f() # {}
f(a=1) # {'a': 1}
f(a=1, b=2, c=3) # {'a': 1, 'b': 2, 'c': 3}
函数的调用之使用 ** 将字典中的每个键值对都转换为关键字实参
调用函数时,可以在字典前添加两个 ** ,从而将字典中的每个键值对都转换为一个单独的关键字实参
def f(a, b, c):
print('a = ', a, 'b = ', b, 'c = ', c)
f(a=1, b=2, c=3) # a = 1 b = 2 c = 3
d = {'a': 1, 'b': 2, 'c': 3}
# 字典 d 整体作为一个位置实参
# f(d) # TypeError: f() missing 2 required positional arguments: 'b' and 'c'
f(a=d['a'], b=d['b'], c=d['c']) # a = 1 b = 2 c = 3
f(**d) # a = 1 b = 2 c = 3
def fun(**kwargs):
print(kwargs)
# 现将字典中的每个键值对都转换为一个单独的关键字实参
# 再用这些关键字实参将个数可变的关键字形参初始化为一个字典
fun(**d) # {'a': 1, 'b': 2, 'c': 3}
总结
def f1(a, b=5, *args, **kwargs):
print('a = ', a, 'b = ', b, 'args = ', args, 'kwargs = ', kwargs)
f1(2, 6, 7, 8, c=9) # a = 2 b = 6 args = (7, 8) kwargs = {'c': 9}
f1(2) # a = 2 b = 5 args = () kwargs = {}
def f2(a, b=5, *, c, **kwargs):
print('a = ', a, 'b = ', b, 'c = ', c, 'kwargs = ', kwargs)
f2(*(3, 6), **{'c': 8, 'd': 10, 'e': 12}) # a = 3 b = 6 c = 8 kwargs = {'d': 10, 'e': 12}
f2(3, c=8, d=10, e=12) # a = 3 b = 5 c = 8 kwargs = {'d': 10, 'e': 12}
pass 语句
pass 语句什么都不做,它只是一个占位符,用在语法上需要的地方,例如:
if 语句的条件执行体、 for in 语句的循环体、定义函数的函数体
有时候我们可能还没有想好上述相关语句具体怎么做,就可以先使用 pass 语句作为占位符,以确保程序可以运行
age = 23
if age > 18:
pass
for i in range(8):
pass
def do_something():
pass
函数的定义之文档字符串
对于函数、模块、类或方法,位于其第一行的字符串被称为文档字符串,通常用 3 个引号表示。
文档字符串用于对函数、模块、类或方法进行解释说明
调用函数时,鼠标悬停在函数名上即可
def do_something():
"""我是函数的说明"""
pass
do_something()
# 可以通过属性 __doc__ 访问文档字符串
print(do_something.__doc__) # 我是函数的说明
print(len.__doc__)
# 调用内置函数 help() 得到的帮助信息中会包含文档字符串
print(help(do_something()))
print(help(len))
函数的文档字符串常见内容和格式约定:
- 第一行是简明扼要的总结
- 第一行的首字母要大写,第一行以句号结尾
- 如果文档字符串包含多行,第二行是空行,从第三行开始是详细的描述
函数的定义之函数注解
定义函数时,为了让形参或返回值的类型或作用更加清晰,可以给形参或返回值添加函数注解,从而对形参或返回值做解释说明,以帮助函数文档化。
函数的注解是可选的,可以是任意的表达式,解释器会忽略掉函数的注解
给形参添加函数注解的方式为:在形参后面添加 : 和任意的表达式
给返回值添加函数注解的方式为:在 ) 后面添加 -> 和任意的表达式
def f(a: 'string type', b: int) -> 'join a with b':
return a + str(b)
print(f('hello', 666)) # hello666
print(f('hello', 'World')) # helloWorld
# 通过属性 __annotations__ 可以访问函数的注解
print(f.__annotations__) # {'a': 'string type', 'b': <class 'int'>, 'return': 'join a with b'}
# 调用内置函数 help() 得到的帮助信息中也会包含函数注解
print(help(f))
递归函数
在一个函数的函数体内,可以调用其他函数,如果调用的函数为该函数本身,该函数就是递归函数
递归函数包含了一种隐式的循环,因此,递归函数必须有一个明确的递归结束条件,也称为递归的出口
能用递归来解决的问题必须满足两个条件:
- 可以通过递归调用来缩小问题的规模,且新问题与原问题有着相同的形式
- 存在一种简单情境,可以使递归在简单情境下退出
# 使用递归计算阶乘
# n! = 1 * 2 * 3 ... * n
def fac(n):
if n == 1:
return 1
return n * fac(n - 1)
print(fac(6)) # 720
# 使用递归计算菲波那切数列
# F0 = 0, F1 = 1 Fn = F(n-1) + F(n-2)(n>=2)
def fib(n):
if n == 0:
return 0
if n == 1:
return 1
return fib(n - 1) + fib(n - 2)
print(fib(6)) # 8