> ## Content Index
> Fetch the complete content index at: https://blog.vercanti.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Python 装饰器与函数高级用法
- URL: https://blog.vercanti.com/python-zhuang-shi-qi-yu-han-shu-gao-ji-yong-fa/
- Published: 2026-08-28T14:34:37.000Z
- Updated: 2026-08-28T14:56:58.000Z
- Description: 不接收 self 或 cls，等同于普通函数，只是命名空间归类在类里。 Python 3.10+ 起静态方法继承原函数的属性（__module__、__name__、__qualname__、__doc__、__annotations__），并新增 __wrapped__ 属性，且可直接作为普通函数调用（无需通过类或实例）。 接收 cls（类本身）而非 self（实例），可在不创建实例的情况下访问类变量。 Python 3.10+ 起类方法继承原函数的属性（__module__、__name__、__qualname__、__doc__、__annota
- Author: yellowdog
- Tags: Python, 基础

> 官方文档：<https://docs.python.org/zh-cn/3/reference/compound%5Fstmts.html#function-definitions>  
> 适用版本：Python 3.10+（2026-05-07 核实）

---

## 一、函数参数详解

### 参数类型全览

```python
def func(pos1, pos2, /, normal, *, kw_only1, kw_only2=10, **kwargs):
    pass

```

| 符号            | 含义                               |
| ------------- | -------------------------------- |
| pos1, pos2, / | / 之前：仅位置参数（Python 3.8+），不能用关键字传入 |
| normal        | 普通参数，可位置可关键字                     |
| \*            | \* 之后：仅关键字参数，必须用关键字传入            |
| \*args        | 收集多余位置参数为元组                      |
| \*\*kwargs    | 收集多余关键字参数为字典                     |

```python
# 仅位置参数（/）
def greet(name, /, greeting='Hello'):
    return f'{greeting}, {name}'

greet('Alice')           # OK
greet('Alice', 'Hi')    # OK
greet(name='Alice')     # TypeError：name 是仅位置参数

# 仅关键字参数（*）
def create_user(*, name, age, admin=False):
    ...

create_user(name='Alice', age=30)   # OK
create_user('Alice', 30)            # TypeError

```

### `*args` 和 `**kwargs`

```python
def func(*args, **kwargs):
    print(args)    # 元组
    print(kwargs)  # 字典

func(1, 2, 3, x=4, y=5)
# args = (1, 2, 3)
# kwargs = {'x': 4, 'y': 5}

# 解包传参
lst = [1, 2, 3]
dct = {'x': 4, 'y': 5}
func(*lst, **dct)

```

### 默认参数陷阱

```python
# 错误：可变默认值是共享的
def append_to(item, lst=[]):   # lst 只创建一次！
    lst.append(item)
    return lst

append_to(1)   # [1]
append_to(2)   # [1, 2]  不是 [2]！

# 正确：用 None 作哨兵
def append_to(item, lst=None):
    if lst is None:
        lst = []
    lst.append(item)
    return lst

```

---

## 二、装饰器

### 基础装饰器

```python
import functools

def my_decorator(func):
    @functools.wraps(func)   # 保留原函数元信息（__name__, __doc__ 等）
    def wrapper(*args, **kwargs):
        print('前置操作')
        result = func(*args, **kwargs)
        print('后置操作')
        return result
    return wrapper

@my_decorator
def greet(name):
    """打招呼"""
    return f'Hello, {name}'

# 等价于
greet = my_decorator(greet)

```

### 带参数的装饰器

```python
def retry(max_times=3, exceptions=(Exception,)):
    """重试装饰器"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_times):
                try:
                    return func(*args, **kwargs)
                except exceptions as e:
                    if attempt == max_times - 1:
                        raise
                    print(f'第 {attempt + 1} 次失败，重试：{e}')
        return wrapper
    return decorator

@retry(max_times=5, exceptions=(ConnectionError, TimeoutError))
def fetch_data(url):
    ...

```

### 类装饰器

```python
class Timer:
    def __init__(self, func):
        functools.update_wrapper(self, func)   # 等价于 @wraps
        self.func = func
        self.call_count = 0

    def __call__(self, *args, **kwargs):
        import time
        start = time.perf_counter()
        result = self.func(*args, **kwargs)
        elapsed = time.perf_counter() - start
        self.call_count += 1
        print(f'{self.func.__name__} 耗时 {elapsed:.4f}s，共调用 {self.call_count} 次')
        return result

@Timer
def slow_func():
    import time
    time.sleep(0.1)

```

### 装饰类的方法

```python
def validate_positive(func):
    @functools.wraps(func)
    def wrapper(self, value, *args, **kwargs):
        if value <= 0:
            raise ValueError(f'{value} 必须为正数')
        return func(self, value, *args, **kwargs)
    return wrapper

class Account:
    @validate_positive
    def deposit(self, amount):
        self.balance += amount

```

### 多个装饰器叠加

```python
@decorator_a
@decorator_b
@decorator_c
def func():
    pass

# 等价于（从下往上包裹）
func = decorator_a(decorator_b(decorator_c(func)))

# 执行时：decorator_a.wrapper → decorator_b.wrapper → decorator_c.wrapper → func

```

---

## 三、常用内置装饰器

### `@property`（见 [内置函数完全参考](https://blog.vercanti.com/python-nei-zhi-han-shu-wan-quan-can-kao/)）

### `@staticmethod`

不接收 `self` 或 `cls`，等同于普通函数，只是命名空间归类在类里。

Python 3.10+ 起静态方法继承原函数的属性（`__module__`、`__name__`、`__qualname__`、`__doc__`、`__annotations__`），并新增 `__wrapped__` 属性，且可直接作为普通函数调用（无需通过类或实例）。

```python
class MathUtils:
    @staticmethod
    def add(a, b):
        return a + b

MathUtils.add(1, 2)   # 3（不需要实例）

```

### `@classmethod`

接收 `cls`（类本身）而非 `self`（实例），可在不创建实例的情况下访问类变量。

Python 3.10+ 起类方法继承原函数的属性（`__module__`、`__name__`、`__qualname__`、`__doc__`、`__annotations__`），并新增 `__wrapped__` 属性。Python 3.9+ 起类方法可包裹其他描述符（如 `property`）。

```python
class Config:
    _instance = None

    @classmethod
    def get_instance(cls):
        if cls._instance is None:
            cls._instance = cls()
        return cls._instance

    @classmethod
    def from_dict(cls, data):
        """另一种构造方式"""
        obj = cls()
        obj.data = data
        return obj

```

### `@functools.lru_cache(maxsize=128, typed=False)`

| 参数      | 说明                                      |
| ------- | --------------------------------------- |
| maxsize | 缓存最大条目数；None 无限制（禁用 LRU，缓存无上限）；128 为默认值 |
| typed   | True 时，不同类型的相同参数（如 1 和 1.0）视为不同缓存键      |

```python
from functools import lru_cache

@lru_cache(maxsize=256)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

fibonacci.cache_info()        # CacheInfo(hits=..., misses=..., maxsize=256, currsize=...)
fibonacci.cache_clear()       # 清除缓存
fibonacci.cache_parameters()  # {'maxsize': 256, 'typed': False}（Python 3.9+）
fibonacci.__wrapped__         # 原始未缓存函数

```

> Python 3.9+ 可用 `@functools.cache`（相当于 `maxsize=None` 的 `lru_cache`，更轻量）。

### `@functools.cached_property` *(Python 3.8+)*

计算一次后缓存到实例 `__dict__`，后续访问直接读属性（比 `@property` \+ `lru_cache` 更简洁）。

```python
class DataProcessor:
    def __init__(self, data):
        self.data = data

    @functools.cached_property
    def result(self):
        print('计算中...')
        return sum(self.data)   # 只计算一次

p = DataProcessor([1, 2, 3])
p.result   # 计算中... → 6
p.result   # 6（直接读缓存，不打印）

# 清除缓存：直接删除属性
del p.result
p.result   # 重新计算

```

> **线程安全注意**（Python 3.12 变更）：3.12 起 `cached_property` 移除了锁机制，在多线程环境下可能被多个线程同时计算（即 getter 被执行多次）。如果对线程安全有要求，请使用 `@property` \+ `lru_cache` 组合，或加锁保护。

### `@dataclasses.dataclass`

```python
from dataclasses import dataclass, field

@dataclass(order=True, frozen=False)
class Point:
    x: float
    y: float
    z: float = 0.0
    tags: list = field(default_factory=list)   # 可变默认值必须用 field

p = Point(1.0, 2.0)
print(p)   # Point(x=1.0, y=2.0, z=0.0, tags=[])

```

**`@dataclass` 参数：**

| 参数            | 默认    | 版本    | 说明                                                                      |
| ------------- | ----- | ----- | ----------------------------------------------------------------------- |
| init          | True  | 3.7+  | 生成 \_\_init\_\_                                                         |
| repr          | True  | 3.7+  | 生成 \_\_repr\_\_                                                         |
| eq            | True  | 3.7+  | 生成 \_\_eq\_\_（基于所有字段）                                                   |
| order         | False | 3.7+  | 生成比较方法（\_\_lt\_\_、\_\_le\_\_、\_\_gt\_\_、\_\_ge\_\_）；eq=False 时不能设为 True |
| unsafe\_hash  | False | 3.7+  | 强制生成 \_\_hash\_\_（即使不安全）；通常用 frozen=True 代替                             |
| frozen        | False | 3.7+  | True 使实例不可变（字段赋值会抛 FrozenInstanceError），且自动可哈希                          |
| match\_args   | True  | 3.10+ | 生成 \_\_match\_args\_\_，用于模式匹配（match/case）                               |
| kw\_only      | False | 3.10+ | 所有字段仅关键字传入                                                              |
| slots         | False | 3.10+ | 生成 \_\_slots\_\_ 节省内存（约 40% 内存减少）                                       |
| weakref\_slot | False | 3.11+ | 在 \_\_slots\_\_ 中添加 \_\_weakref\_\_ 槽，允许对实例创建弱引用（需 slots=True）          |

---

## 四、闭包

闭包是引用了外层作用域变量的函数，外层函数返回后变量仍存活：

```python
def make_counter(start=0):
    count = start

    def increment(step=1):
        nonlocal count   # 声明引用外层变量（而非创建局部变量）
        count += step
        return count

    return increment

counter = make_counter(10)
counter()    # 11
counter(5)   # 16

```

### `nonlocal` vs `global`

| 关键字        | 作用域                 |
| ---------- | ------------------- |
| nonlocal x | 引用最近的外层（非全局）作用域中的 x |
| global x   | 引用模块全局作用域中的 x       |

```python
# 陷阱：循环变量捕获
funcs = [lambda: i for i in range(3)]
[f() for f in funcs]   # [2, 2, 2]  不是 [0, 1, 2]！

# 修复：用默认参数捕获当前值
funcs = [lambda i=i: i for i in range(3)]
[f() for f in funcs]   # [0, 1, 2]

```

---

## 五、高阶函数技巧

### `functools.partial(func, *args, **kwargs)`

预填充部分参数，创建新的可调用对象：

```python
from functools import partial

def power(base, exp):
    return base ** exp

square = partial(power, exp=2)
cube = partial(power, exp=3)

square(5)   # 25
cube(3)     # 27

# 实用场景：配置特定行为的函数
import json
json_pretty = partial(json.dumps, indent=2, ensure_ascii=False)

```

**Python 3.14+ 新增 `Placeholder` 支持**：可预填充任意位置的参数，不限于前导位置。

```python
from functools import partial, Placeholder as _

# 预填充第三个参数，前两个留空
say_to_world = partial(print, _, _, "world!")
say_to_world('Hello', 'dear')  # Hello dear world!

# 预填充第二个参数
divide_by = partial(lambda a, b: a / b, _, 2)
divide_by(10)   # 5.0

```

### `functools.reduce(function, iterable, initializer=None)`

累积计算：

```python
from functools import reduce
reduce(lambda acc, x: acc + x, [1, 2, 3, 4], 0)  # 10
reduce(lambda a, b: a * b, range(1, 6))            # 120（5!）

```

### `operator` 模块代替 lambda

```python
import operator

sorted(items, key=operator.itemgetter('score'))    # 比 lambda x: x['score'] 快
sorted(items, key=operator.attrgetter('name'))     # 属性访问
sorted(items, key=operator.itemgetter(2, 0))       # 多字段：先索引2，再索引0

from operator import add, mul
reduce(add, [1, 2, 3])   # 6

```

---

## 六、异步函数

### `async def` / `await`

```python
import asyncio

async def fetch(url: str) -> str:
    await asyncio.sleep(1)   # 模拟 IO
    return f'data from {url}'

async def main():
    # 串行（共 2 秒）
    result1 = await fetch('url1')
    result2 = await fetch('url2')

    # 并发（共 1 秒）
    result1, result2 = await asyncio.gather(
        fetch('url1'),
        fetch('url2')
    )

```

### 异步装饰器

```python
def async_retry(max_times=3):
    def decorator(func):
        @functools.wraps(func)
        async def wrapper(*args, **kwargs):
            for attempt in range(max_times):
                try:
                    return await func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_times - 1:
                        raise
                    await asyncio.sleep(0.1 * (attempt + 1))
        return wrapper
    return decorator

@async_retry(max_times=3)
async def fetch_with_retry(url):
    ...

```

### 异步上下文管理器

```python
class AsyncDB:
    async def __aenter__(self):
        self.conn = await connect()
        return self.conn

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        await self.conn.close()
        return False   # 不吞噬异常

async with AsyncDB() as conn:
    await conn.execute(...)

```

---

## 七、实用技巧

### 函数签名检查

```python
import inspect

sig = inspect.signature(func)
for name, param in sig.parameters.items():
    print(name, param.kind, param.default)

```

### `__slots__` 节省内存

```python
class Point:
    __slots__ = ('x', 'y')   # 禁用 __dict__，节省约 40% 内存

    def __init__(self, x, y):
        self.x = x
        self.y = y

```

### 单分派泛型函数

Python 3.7+ 起支持类型注解注册方式；Python 3.11+ 起支持 `typing.Union` 和 `X | Y` 联合类型注册。

```python
from functools import singledispatch

@singledispatch
def process(data):
    raise NotImplementedError(f'不支持类型 {type(data)}')

@process.register(str)
def _(data):
    return data.upper()

@process.register(list)
def _(data):
    return [process(x) for x in data]

@process.register(int)
@process.register(float)
def _(data):
    return data * 2

# Python 3.11+：使用注解语法注册联合类型
@process.register
def _(data: int | float):
    return data * 2

```

---

## 最佳实践

**使用 `@functools.wraps(func)` 保留被装饰函数的元信息**：不加 `@wraps` 时，装饰后函数的 `__name__`、`__doc__`、`__annotations__` 会变成 wrapper 函数的属性，导致文档工具和调试信息失真。

```python
import functools

def my_decorator(func):
    @functools.wraps(func)  # 必须加
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

```

**用 `*args, kwargs` 让装饰器对任意签名的函数兼容**：固定参数签名的装饰器只能装饰特定签名的函数，`*args, **kwargs` 透传参数使装饰器通用。

**装饰器需要带参数时，使用三层嵌套函数（装饰器工厂）**：外层函数接收参数，中层返回真正的装饰器，内层是 wrapper，逻辑清晰。

```python
def retry(times=3, delay=1.0):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for i in range(times):
                try:
                    return func(*args, **kwargs)
                except Exception:
                    if i == times - 1:
                        raise
                    time.sleep(delay)
        return wrapper
    return decorator

@retry(times=5, delay=0.5)
def fetch_data(): ...

```

**可变默认参数用 `None` 代替**：`def f(x, data=[])` 中默认 `data` 是所有调用共享的同一列表对象，应改为 `def f(x, data=None): data = data or []`。

**使用 `functools.lru_cache` 缓存纯函数结果，而非手写缓存字典**：`@lru_cache` 线程安全、自动管理 LRU 淘汰，无需手动维护缓存逻辑。

---

## 常见陷阱

### 陷阱：闭包变量捕获的是变量引用，而非值快照

**现象：** 在循环中用 lambda 或嵌套函数创建函数列表，所有函数执行时使用的都是循环结束后的最终值。

**原因：** Python 闭包捕获的是变量本身（引用），不是创建闭包时的值。循环变量 `i` 在所有闭包中共享同一个绑定。

**解决：** 用默认参数将当前值"固化"：`lambda i=i: i`，或用 `functools.partial`。

```python
# 错误：所有函数都打印 9
funcs = [lambda: i for i in range(10)]
print(funcs[0]())  # 9，而非 0

# 正确：用默认参数固化当前值
funcs = [lambda i=i: i for i in range(10)]
print(funcs[0]())  # 0

```

### 陷阱：可变默认参数在多次调用间共享

**现象：** 函数多次调用后，默认列表参数积累了之前调用的数据，产生意外输出。

**原因：** 函数定义时，默认参数值只求值一次，列表/字典对象在所有调用间共享。

**解决：** 默认值改为 `None`，函数体内创建新对象。

```python
# 错误
def append(item, lst=[]):
    lst.append(item)
    return lst

# 正确
def append(item, lst=None):
    if lst is None:
        lst = []
    lst.append(item)
    return lst

```

### 陷阱：装饰器未加 `@wraps` 导致 `__name__` 变为 `wrapper`

**现象：** pytest 无法识别测试函数名，Flask 路由注册报"视图函数重名"，日志中函数名全是 `wrapper`。

**原因：** 没有 `@functools.wraps(func)`，装饰后的函数对象的 `__name__` 是内部 `wrapper` 函数的名称。

**解决：** 始终在 wrapper 函数上添加 `@functools.wraps(func)`。

---

## 参见

- [asyncio异步编程完全指南](https://blog.vercanti.com/asyncio-yi-bu-bian-cheng-wan-quan-zhi-nan/)
- [FastAPI完全指南](https://blog.vercanti.com/fastapi-wan-quan-zhi-nan/)
- [pytest完全指南](https://blog.vercanti.com/pytest-wan-quan-zhi-nan/)
- [数据类型](https://blog.vercanti.com/python-nei-zhi-shu-ju-lei-xing-wan-quan-can-kao/)