# 递归

递归是什么?
举个例子:

def demo(n:int):
    count = 1
    if n ==100:
        return count
    else:
        return count + demo(n+1)
print(demo(2)) # 输出 99

约等于循环 99 次 + 1
在函数中调用函数本身就是递归

递归必须要准备一个退出分支,例如本段的 if n==100: 就是退出分支

如果没有退出分支:

def demo(n:int):
    count = 1
    return count + demo(n+1)
# RecursionError: maximum recursion depth exceeded

python 有最大递归深度,一般为 1000 层,部分解释器由于配置可能会有 ±10 层的区别
假如这个函数在正常情况的递归深度超过了 1000 层,可以使用下面的代码调整最大深度

import sys
sys.setrecursionlimit(10000) # 层数

然后你就会发现:代码执行不完就会报错 进程已结束,退出代码-1073741571 (0xC00000FD)
这是因为栈溢出了,建议减少递归层数或进行尾递归优化

生成器在后面会讲到

# 匿名表达式和函数变量

# 匿名表达式

a = lambda n: n * 10
print(a(10))

这种写法 PEP 并不建议,它会建议使用 def 来实现效果,但部分情况下这样会徒增行数
并不会增加可读性

匿名表达式格式: lambda 输入:输出
建议将 lambda 用于简易函数,大型函数使用 def

# 警卫

在 lambda 后可以使用 if...else (警卫) 来实现分支结构
例子:

f = lambda a: a*10 if a%2 == 0 else a*5
print(f(10))
print(f(5))

警卫的格式: 返回值 if 条件 else 返回值(可以再套一个if)
可用于 return 等返回值的关键字后

套的层多了可读性很差

# 函数变量

def demo():
    print("hello")
func = demo
func()

变量名 = 方法 即为函数变量格式

方法后不带括号,不然就是把返回值作为赋值对象

直接用 变量名() 就可以调用方法

# 推导式

f = [x for x in range(10)]
print(f) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

推导式格式: 表达式 for 变量 in 可迭代数据
警卫同样适用于表达式,如下:

f = [x*2 if x%2 == 0 else x for x in range(10)]
print(f)

# 迭代器和生成器

# 迭代器

list1 = [1,2,3,4,5]
iter1 = iter(list1) # 或 list1.__iter__()
print(iter1.__next__()) # 或 next (iter1)

对可迭代对象执行 iter() 操作,就可以得到一个迭代器
对迭代器执行 next() 方法,就会让指针前进一项并返回
在指针超出范围后,会返回 StopIteration 错误
一个示意图
迭代器非常省内存,比如一个有一千万个整数的列表,使用列表会占用 100MB 左右,而迭代器只有 50B 左右
因为迭代器是惰性的,执行前不会访问
可以使用 for 循环来快速访问迭代器:

l = [1,2,3,4,5]
i = iter(l)
for j in i:
    print(j)

# 生成器

# 生成器函数

def fibonacci(num: int):
    a, b = 1, 1
    for i in range(num - 1):
        a, b = b, a + b
        yield a

这是一个生成斐波那契数列的函数,尝试直接 print 它:

print(fibonacci(10))

返回: <generator object fibonacci at 0x0000022C97099B60>
实际上这个函数已经变成了生成器,可以使用 for 或 next 来获取返回值
生成器的语法:

def 函数名(参数):
    语句
    yield 返回值
    语句

与 return 不同,函数遇到 yield 后不会退出,在返回值后继续执行下面的代码
所以可以有多个 yield, 返回值格式如下:

值 1 值 2 值 3 值 1 值 2 值 3...