文章目录
递归函数/尾递归优化
如果一个函数在内部调用自身本身,这个函数就是递归函数。
例如 n! 阶乘:
def fact(n):
if n==1:
return 1
return n * fact(n - 1) # 此处return语句是一个表达式,即乘法表达式
如果我们计算fact(5),可以根据函数定义看到计算过程如下:
===> fact(5)
===> 5 * fact(4)
===> 5 * (4 * fact(3))
===> 5 * (4 * (3 * fact(2)))
===> 5 * (4 * (3 * (2 * fact(1))))
===> 5 * (4 * (3 * (2 * 1)))
===> 5 * (4 * (3 * 2))
===> 5 * (4 * 6)
===> 5 * 24
===> 120
递归函数的优点是定义简单,逻辑清晰;缺点是过深的调用会导致栈溢出。
在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。
尾递归优化
尾递归是指在函数返回的时候,调用自身本身,并且return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
def fact(n):
return fact_iter(n, 1) # 初始product = 1,即乘以任何数都是其本身
def fact_iter(num, product):
if num == 1:
return product
return fact_iter(num - 1, num * product)
可以看到,return fact_iter(num - 1, num * product)仅返回递归函数本身,num - 1和num * product在函数调用前就会被计算,不影响函数调用。
fact(5)对应的fact_iter(5, 1)的调用如下:
===> fact_iter(5, 1)
===> fact_iter(4, 5)
===> fact_iter(3, 20)
===> fact_iter(2, 60)
===> fact_iter(1, 120)
===> 120
尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。但是,Python标准的解释器没有针对尾递归做优化,任何递归函数都存在栈溢出的问题。
【练习汉诺塔移动】
有三个立柱A、B、C。A柱上穿有大小不等的圆盘N个,较大的圆盘在下,较小的圆盘在上。要求把A柱上的圆盘全部移到C柱上,保持大盘在下、小盘在上的规律(可借助B柱)。每次移动只能把一个柱子最上面的圆盘移到另一个柱子的最上面。请输出移动过程。
# 利用递归函数移动汉诺塔
def Hanoi(n, a, b, c):
if n == 1:
print('move', a, '-->', c)
else:
Hanoi(n-1, a, c, b)
Hanoi(1, a, b, c)
Hanoi(n-1, b, a, c)
# 执行函数
Hanoi(3, 'A', 'B', 'C')
输出结果为:
move A --> C
move A --> B
move C --> B
move A --> C
move B --> A
move B --> C
move A --> C
【解答原理】汉诺塔问题,可以通过三步实现:
- 将A柱最上面的n-1个圆盘移动到B柱(借助C柱)
- 将A柱上剩下的那1个圆盘直接移动到C柱
- 将B柱上的n-1个圆盘移动到C柱(借助A柱)
如下图所示(from柱即A柱, by柱即B柱, to柱即C柱):
参考文章:
递归函数
python汉诺塔
生成器
在Python中一边循环一边计算的机制称为生成器(generator)。
把一个列表生成式的[]改成(),就创建了一个generator,然后通过for循环来迭代它。
斐波拉契数列: 1, 1, 2, 3, 5, 8, 13, 21, 34, …
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b # yield关键字,表示是一个generator
a, b = b, a + b
n = n + 1
return 'done'
# 调用
for i in fib(6):
print(i)
变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:
g = fib(6)
while True:
try:
x = next(g)
print('g:', x)
except StopIteration as e:
print('Generator return value:', e.value)
break
输出结果
g: 1
g: 1
g: 2
g: 3
g: 5
g: 8
Generator return value: done
装饰器
参考:Python 函数装饰器
Python装饰器
简单地说,装饰器是修改其他函数的功能的函数。
举例:
下面是没有装饰器的代码:
def a_new_decorator(a_func):
def wrapTheFunction():
print("I am doing some boring work before executing a_func()")
a_func()
print("I am doing some boring work after executing a_func()")
return wrapTheFunction
def a_function_requiring_decoration():
print("I am the function which needs some decoration to remove my foul smell")
a_function_requiring_decoration()
#outputs: "I am the function which needs some decoration to remove my foul smell"
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
#now a_function_requiring_decoration is wrapped by wrapTheFunction()
a_function_requiring_decoration()
#outputs:I am doing some boring work before executing a_func()
# I am the function which needs some decoration to remove my foul smell
# I am doing some boring work after executing a_func()
python装饰器,即封装一个函数并修改它的行为。下面是改装后的python解释器代码:
from functools import wraps
def a_new_decorator(a_func):
@wraps(a_func)
def wrapTheFunction():
print("I am doing some boring work before executing a_func()")
a_func()
print("I am doing some boring work after executing a_func()")
return wrapTheFunction
@a_new_decorator # 装饰器
def a_function_requiring_decoration():
print("I am the function which needs some decoration to remove my foul smell")
a_function_requiring_decoration()
# 作用等同于a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
#outputs: I am doing some boring work before executing a_func()
# I am the function which needs some decoration to remove my foul smell
# I am doing some boring work after executing a_func()
再举一个案例:定义一个打印出方法名及其参数的装饰器。
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('call %s():' % func.__name__)
print('args = {}'.format(*args))
return func(*args, **kwargs)
return wrapper
调用装饰器
@log
def test(p):
print(test.__name__ + " param: " + p)
test("I'm a param")
# 输出
# call test():
# args = I'm a param
# test param: I'm a param
值得注意的是@functools.wraps(func),这是python提供的装饰器。它能把原函数的元信息拷贝到装饰器里面的 func 函数中。函数的元信息包括docstring、name、参数列表等等。
装饰器功能:
- 引入日志
- 函数执行时间统计
- 执行函数前预备处理
- 执行函数后清理功能
- 权限校验等场景
- 缓存
*args和**kwargs
参考:Python中的*args和**kwargs
*args 就是传递一个可变参数列表给函数实参,这个参数列表的数目未知,甚至长度可以为0。
例如:
def test_args(first, *args): # 第一个参数是必须要传入的参数,所以使用了第一个形参,后面几个是可变参数列表
print('Required argument: ', first)
print(type(args))
for v in args:
print ('Optional argument: ', v)
test_args(1, 2, 3, 4)
输出:
Required argument: 1
<class ‘tuple’>
Optional argument: 2
Optional argument: 3
Optional argument: 4
**kwargs是将一个可变的关键字参数的字典传给函数实参,同样参数列表长度可以为0或为其他值。
例如:
def test_kwargs(first, *args, **kwargs):
print('Required argument: ', first)
print(type(kwargs))
for v in args:
print ('Optional argument (args): ', v)
for k, v in kwargs.items():
print ('Optional argument %s (kwargs): %s' % (k, v))
test_kwargs(1, 2, 3, 4, k1=5, k2=6)
输出:
Required argument: 1
<class ‘dict’>
Optional argument (args): 2
Optional argument (args): 3
Optional argument (args): 4
Optional argument k1 (kwargs): 5
Optional argument k2 (kwargs): 6
args类型是一个tuple,而kwargs则是一个字典dict,并且args只能位于kwargs的前面。
常用的一些语法功能
%load_ext autoreload
设置修改的模块自动重新载入
%load_ext autoreload
%autoreload 2
sys.path.append()用法
参考:关于sys.path.append()
当我们导入一个模块时:import xxx,默认情况下python解析器会搜索当前目录、已安装的内置模块和第三方模块,搜索路径存放在sys模块的path中,可通过sys.path
查看。
对于模块和自己写的脚本不在同一个目录下,在脚本开头加sys.path.append(‘xxx’):
import sys
sys.path.append(’引用模块的地址')
把路径添加到系统的环境变量,或把该路径的文件夹放进已经添加到系统环境变量的路径内。环境变量的内容会自动添加到模块搜索路径中。可以通过dir(sys)来查看他里面的方法和成员属性。
os.listdir() 方法
os.listdir() 方法用于返回指定的文件夹包含的文件或文件夹的名字的列表。
import os, sys
print(os.listdir('查看的文件夹路径'))
os.chdir()
os.chdir() 方法用于改变当前工作目录到指定的路径。
os.chdir(path) # path为要切换到的新路径