python自学笔记第五节---递归 + 闭包 + 装饰器


---

## 一、递归

实现 1 到 100 的累加和。

### 方法一:循环

```python
def add(n):
sum = 0
for i in n:
sum += i
return sum
sum = add(range(1, 101))
print(sum) # 5050
```

### 方法二:递归函数

```python
def add2(n):
if n == 1:
return 1
return n + add2(n - 1)

sum = add2(100)
print(sum) # 5050
```

- 递归三要素:**出口**(`n == 1`)+ **递推公式**(`n + add2(n-1)`)+ **参数收敛**(每次减 1)。
- 对比 Java:语法几乎一致,Python 默认递归深度限制 1000(`sys.getrecursionlimit()`),Java 没有这种保护,深度过大会 `StackOverflowError`。

---

## 二、闭包

### 2.1 闭包三条件

1. **函数嵌套**(函数里面再定义函数)
2. **内层函数使用外层函数的局部变量**
3. **外层函数的返回值是内层函数的函数名**

```python
def outer():
n = 10
def inner(): # 1. 函数嵌套
print(n) # 2. 内层函数使用外层函数的局部变量
return inner # 3. 外层函数的返回值是内层函数的函数名

# 调用方法
# 1、直接调用
outer()() # 10

# 2、间接调用
ot = outer()
ot() # 10

# 只写一层
print(outer()) # <function outer.<locals>.inner at 0x...> 返回的是内层函数的内存地址
```

### 2.2 闭包传参

```python
def outer(m):
n = 10
def inner(t): print(m + n + t)
return inner
ot = outer(20)
ot(5) # 35 通过引用调用函数
outer(20)(40) # 70
print(id(ot)) # ot 相当于 inner,返回的是 inner 的内存地址(引用)
```

### 2.3 通过引用调用函数

```python
def test():
print("这是test方法")
te = test
te() # 通过引用调用函数
```

- **函数名本质是引用**:Python 中函数是一等公民,可以赋值、传参、返回。Java 8 之前方法不是一等公民,必须靠反射或函数式接口(`Supplier / Function`)才能传递。
- 闭包让外层函数的局部变量"延长生命周期",类似 Java 的 Lambda 捕获外部变量(但 Java 要求 effectively final)。

---

## 三、装饰器

**核心思想**:在不改变原有代码的情况下新增新功能,主要不是复用。
**原理**:装饰器的原理就是将原有的函数名重新定义为以原函数为参数的闭包。

### 3.1 函数作为参数传递

#### 示例一:传入函数返回值

```python
def test2():
print("这是test2")

def test3(fn): # 传入函数
print("这是test3")
fn

test3(test2()) # 先打印 这是test2,再打印 这是test3
# java 不能这样写 但感觉这个例子很干,极限情况下才会用,毕竟直接在方法里面调用方法就行了
```

#### 示例二:在方法内部调用

```python
def test2():
print("这是test2q")

def test3(): # 传入函数
print("这是test3q")
test2()
test3() # 先打印 这是test3q,再打印 这是test2q
```

#### 示例三:传入函数引用

```python
def test2():
print("这是test2")

def test3(fn): # 传入函数
print("这是test3")
fn()

test3(test2) # 先打印 这是test3,再打印 这是test2
```

- **关键区别**:`test3(test2())` 传的是**返回值**(已执行),`test3(test2)` 传的是**函数引用**(延迟执行)。
- Java 没有这种"传方法"的语法糖,最接近的是 `Supplier<T>` / `Runnable`。

### 3.2 标准版装饰器

```python
# 被装饰的函数
def test():
return "发送消息"

# 装饰器函数
def outer(fn):
# 既包含原有功能,又包含新功能
def inner():
print("登录", end=" ")
# 执行被装饰的函数
print(fn())
return inner

ot = outer(test)
ot() # 登录 发送消息

def test2():
return "转账520"

ot = outer(test2)
ot() # 登录 转账520
```

### 3.3 语法糖 `@`

```python
# 格式 @装饰器名称
def outer(fn):
def inner():
print("登录", end=" ")
fn()
return inner

@outer # 等价于 ot = outer(test2),要紧挨着下面方法写
def test2():
print("发送消息 hahaha")

test2() # 登录 发送消息 hahaha
```

- `@outer` 是语法糖,等价于 `test2 = outer(test2)`,但更优雅。
- 对应 Java 的注解 `@Override`、Spring 的 `@Transactional`,但 Python 装饰器是**运行时函数包装**,Java 注解需要配合 AOP 框架(如 Spring AOP、AspectJ)才能生效。

### 3.4 被装饰函数带参数

```python
# 被装饰的函数 传入形参 可变参数 args
def func(*args):
print(args)

# 装饰器
def outer(fn):
def inner(*args):
fn(*args)
return inner

# 标准装饰器调用
# 方法一
ot = outer(func)
ot(1, 2, 3, 4, 5) # (1, 2, 3, 4, 5)

# 方法二
outer(func)(1, 2, 3, 4) # (1, 2, 3, 4)

# 语法糖调用装饰器函数
@outer
def func(*args):
print(args)
func(1, 2, 3, 4, 599) # (1, 2, 3, 4, 599)
```

- 用 `*args` 接收任意参数,让装饰器适配所有签名的被装饰函数。

### 3.5 多个装饰器叠加

```python
def outer(fn):
def inner():
print("这是第一个装饰器", end=" ")
fn()
return inner

def outer1(fn):
def inner():
print("这是第二个装饰器", end=" ")
fn()
return inner

def outer2(fn):
def inner():
print("这是第三个装饰器", end=" ")
fn()
return inner

# 以语法糖写法做示例
@outer2
@outer1
@outer
def func():
print("这是被装饰的函数", end=" ")
func() # 这是第三个装饰器 这是第二个装饰器 这是第一个装饰器 这是被装饰的函数
```

**执行原理:**

```
outer2(func) ← 这一刻就打印"第三个"(还没调到最终函数!)
() ← 打印"第二个"
() ← 打印"第一个" -> 打印"原始函数"
```

- 装饰顺序**从下往上**装饰(`@outer` 先包住 `func`),但执行时**从上往下**层层进入。
- 类似 Java 多层 AOP,外层切面先进后出。

### 3.6 嵌套函数等价写法

```python
def outer2(fn):
print("这是第三个装饰器", end=" ")
def outer1():
print("这是第二个装饰器", end=" ")
def outer():
print("这是第一个装饰器", end=" ")
fn()
return outer
return outer1

def func():
print("这是被装饰的函数", end=" ")
outer2(func)()() # 这是第三个装饰器 这是第二个装饰器 这是第一个装饰器 这是被装饰的函数
```

- 多装饰器本质上就是多层闭包嵌套,手动展开就能看清调用链。

---

## 📌 与 Java 的对比

| 概念 | Java | Python |
|------|------|--------|
| 递归 | 方法递归,可能 `StackOverflowError` | 函数递归,默认深度 1000 |
| 函数一等公民 | 否(需函数式接口包装) | 是(可直接传参/返回) |
| 闭包 | Lambda 捕获 effectively final 变量 | 内层函数捕获外层局部变量 |
| 装饰器 | 注解 `@Xxx` + AOP 框架 | `@xxx` 语法糖 + 闭包,原生支持 |
| AOP 切面 | Spring AOP / AspectJ | 装饰器直接搞定 |

> Java 同学理解装饰器的最佳方式:把它当成"不需要框架、运行时即时生效的 AOP"。`@outer` 不是声明式注解等待扫描,而是一执行就把函数重新绑定成包装后的版本。

---
posted @ 2026-07-25 22:27  爱笑的橘子  阅读(2)  评论(0)    收藏  举报