Python基础之函数
本节将介绍函数的基本概念,定义,调用,多返回值,默认参数,可变参数,拆包,递归,lambda等内容。
概念
在任何一门编程语言中,函数都是非常重要的内容。函数的功能有以下几点:
- 拆分业务逻辑。函数可以将一个复杂的业务逻辑拆分成若干个小的逻辑,每一个逻辑中只做一件事情。好处是结构清晰,便于重构,易于维护。
- 代码复用。有了函数就可以不用写太多重复的代码,调用函数即可。不管是自己复用自己的代码,还是将自己的代码发布给其他人,以函数为单位都是可以的。
设计代码结构时需要注意,函数应该是功能的最小集合,一个函数只完成一件事情。不同层次的代码应该放在不同的函数中,层次结构不同的代码不能放到同一个函数中。
函数的定义和调用
在Python中,使用关键字 def 定义一个函数,函数命名和变量命名一样,通常不会有大写字母,以下划线分割,首字母不能是数字。例如:
def cal_num(num):
if num <= 0:
return 0
result = 0
for i in range(num):
result += (i + 1)
return result
print(cal_num(100))
该函数的函数名为 cal_num,通过输入一个数字,可以计算从1到该数字的和,并返回。调用函数时需要给予相关的参数。
运行结果:
D:\work\python_workspace\python_study\venv\Scripts\python.exe D:/work/python_workspace/python_study/basic_04/def_usage.py
5050
Process finished with exit code 0
多返回值
函数可以返回多个值,其本质是返回了一个tuple。例如:
def cal_num(num):
if num <= 0:
return 0
result_sum = 0
result_multi = 1
for i in range(num):
result_sum += (i + 1)
result_multi *= (i + 1)
return result_sum, result_multi
sum, multi = cal_num(10)
print(sum)
print(multi)
result = cal_num(10)
print(result)
print(type(result))
通过修改上面的函数,使得cal_num可以返回两个值,一个是1到N的和,另一个是1到N的积。获取返回值的时候可以给一个变量,其结果是一个元组,也可以给相同个数的变量以接受对应的返回值。注意:如果获取返回值时指定的变量个数既不是1,也不是函数返回值的个数,那么程序会报错。
运行结果:
D:\work\python_workspace\python_study\venv\Scripts\python.exe D:/work/python_workspace/python_study/basic_04/multi_return.py
55
3628800
(55, 3628800)
<class 'tuple'>
Process finished with exit code 0
默认参数
在定义函数时可以指定一个或多个默认参数,在调用时如果不传递该参数那么在函数执行时将使用默认值。例如:
def cal_num(num=10):
if num <= 0:
return 0
result = 0
for i in range(num):
result += (i + 1)
return result
print(cal_num(100))
print(cal_num())
第二次调用cal_num时就没有传递参数,此时将使用默认参数10。
运行结果:
D:\work\python_workspace\python_study\venv\Scripts\python.exe D:/work/python_workspace/python_study/basic_04/default_args.py
5050
55
Process finished with exit code 0
注意,函数的默认参数后面如果还有其它参数,这些参数都必须带有默认值。也就是说默认参数应该尽量的靠右,否则的话可能有歧义。例如:
def cal_num(num=10, other_var):
pass
print(cal_num(100))
print(cal_num())
这时执行会报错,因为函数不知道传入的值100到底是给第一个参数num,还是给第二个参数other_var。
D:\work\python_workspace\python_study\venv\Scripts\python.exe D:/work/python_workspace/python_study/basic_04/default_args2.py
File "D:/work/python_workspace/python_study/basic_04/default_args2.py", line 1
def cal_num(num=10, other_var):
^
SyntaxError: non-default argument follows default argument
Process finished with exit code 1
可变参数
可变参数的使用场景是可以给函数传递多个值,值的个数在定义函数时并不确定,同时在调用时也可能是变化的。可变参数有两种形式:
- 参数前加符号 *,用于接收一个元组。一般用于传入一般的参数不确定时。
- 参数前加符号 **,用于接收一个字典。一般用于传入的关键字参数不确定时。
一个函数中只能有一个元组和字典的可变参数,且通常放到函数的最右侧。例如:
def cal_num(*num):
result = 0
for i in num:
result += i
return result
print(cal_num(1, 2, 3))
print(cal_num(3, 4, 6, 8))
cal_num可接收多个参数,用于计算这些值的和,并返回。
运行结果:
D:\work\python_workspace\python_study\venv\Scripts\python.exe D:/work/python_workspace/python_study/basic_04/variable_args.py
6
21
Process finished with exit code 0
tuple和dict的结合使用:
def print_info(*args, **kwargs):
print(args)
print(kwargs)
print_info(1, 2, 3, name='xiaoming', age=30)
运行结果:
D:\work\python_workspace\python_study\venv\Scripts\python.exe D:/work/python_workspace/python_study/basic_04/variable_args2.py
(1, 2, 3)
{'name': 'xiaoming', 'age': 30}
Process finished with exit code 0
可见,args接收到了所有的一般参数,在函数中作为tuple来处理。kwargs接收到了所有的关键字参数,在函数中作为dict来处理。
拆包
对于上面讲到的可变参数,可以将某个tuple或list变量传入到*args,将某个dict变量传入到**kwargs。在传入这些变量时需要拆包,否则两个变量都会被传入到第一个参数中。例如:
def print_info(*args, **kwargs):
print(args)
print(kwargs)
t = 1, 2, 3
d = {'name': 'xiaoming', 'age': 30}
print_info(t, d)
运行结果:
D:\work\python_workspace\python_study\venv\Scripts\python.exe D:/work/python_workspace/python_study/basic_04/variable_args3.py
((1, 2, 3), {'name': 'xiaoming', 'age': 30})
{}
Process finished with exit code 0
可以看出,args接收到了元组和dict两个参数,并将这两个参数组合成一个新元组。这显然不是我们想要的,要想得到正确的值,需要对元组和dict拆包。在元组变量前增加符号 *,在字典变量前增加符号 **。例如:
def print_info(*args, **kwargs):
print(args)
print(kwargs)
t = 1, 2, 3
d = {'name': 'xiaoming', 'age': 30}
print_info(*t, **d)
运行结果:
D:\work\python_workspace\python_study\venv\Scripts\python.exe D:/work/python_workspace/python_study/basic_04/variable_args4.py
(1, 2, 3)
{'name': 'xiaoming', 'age': 30}
Process finished with exit code 0
递归
递归就是函数自己调用自己,使用场景是某个问题的解可以分解成一个较小规模的问题和一个常规的处理逻辑。而较小规模的问题又可以继续分解为更小规模的问题和一个常规的处理逻辑。直到最后将原问题分解成一个不可分割的已知条件。例如,最经典的斐波拉契数列(Fibonacci),定义数列满足以下条件:
假设求解,根据公式可知,,,,所以,
递归必须满足两点:
- 有递归表达式,即原问题可以分解成子问题
- 有退出条件,即初始条件,否则递归无法退出
在程序设计中,一般用栈空间存放递归函数调用过程中的上下文信息,而栈空间是有限的,所以要避免出现深层次的调用,否则会出现栈空间溢出。
很多时候可以采用空间换时间的思想来减少递归调用次数。例如上面的斐波拉契数列,在计算完某个的值以后可以将该值放入内存,后面需要的时候可以直接使用,而不需要再次计算。
斐波拉契代码实现:
def fibonacci(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(4))
print(fibonacci(10))
运行结果:
D:\work\python_workspace\python_study\venv\Scripts\python.exe D:/work/python_workspace/python_study/basic_04/fibonacci.py
3
55
Process finished with exit code 0
空间换时间代码实现:
import time
# Used to store fibonacci temp data
fibonacci_tmp_data = {}
def fibonacci(n):
"""
The original fibonacci function, no cache
:param n: A integer number
:return: Fibonacci result
"""
if n == 0:
return 0
elif n == 1:
return 1
else:
return fibonacci(n - 1) + fibonacci(n - 2)
def fibonacci_2(n):
"""
The enhanced fibonacci function, use cache
:param n: A integer number
:return: Fibonacci result
"""
if n in fibonacci_tmp_data:
return fibonacci_tmp_data[n]
result = 0
if n == 0:
result = 0
elif n == 1:
result = 1
else:
result = fibonacci_2(n - 1) + fibonacci_2(n - 2)
fibonacci_tmp_data[n] = result
return result
num = 38
start = time.time()
print("fibonacci(%d) = %d" % (num, fibonacci(num)))
end = time.time()
print("Spend %.2f seconds." % (end - start))
print()
start = time.time()
print("fibonacci_2(%d) = %d" % (num, fibonacci_2(num)))
end = time.time()
print("Spend %.2f seconds." % (end - start))
print()
num = 1000
start = time.time()
print("fibonacci_2(%d) = %d" % (num, fibonacci_2(num)))
end = time.time()
print("Spend %.2f seconds." % (end - start))
运行结果:
D:\work\python_workspace\python_study\venv\Scripts\python.exe D:/work/python_workspace/python_study/basic_04/fibonacci2.py
fibonacci(38) = 39088169
Spend 14.61 seconds.
fibonacci_2(38) = 39088169
Spend 0.00 seconds.
fibonacci_2(1000) = 43466557686937456435688527675040625802564660517371780402481729089536555417949051890403879840079255169295922593080322634775209689623239873322471161642996440906533187938298969649928516003704476137795166849228875
Spend 0.00 seconds.
Process finished with exit code 0
可见,用传统方式计算斐波拉契数列,当N为38时就需要花费14.61秒,随着N的增大,计算的时间复杂度将呈现指数级增长,其复杂度为。而采用空间换时间的思想后,当N为1000时总的时间也几乎为0,其时间复杂度仅为。更多计算细节可参考:https://blog.csdn.net/lxf_style/article/details/80458519
lambda
lambda可定义匿名函数,其存在的意义是可以减少函数定义时的代码数量,从而看上去比较简单。一般用于定义逻辑比较简单的函数,在复杂的场景下不推荐使用。lambda不会提高程序执行效率。例如:
f = lambda x, y: x + y
print(f(3, 4))
运行结果:
D:\work\python_workspace\python_study\venv\Scripts\python.exe D:/work/python_workspace/python_study/basic_04/lambda.py
7
Process finished with exit code 0