Python 装饰器与函数高级用法

不接收 self 或 cls,等同于普通函数,只是命名空间归类在类里。 Python 3.10+ 起静态方法继承原函数的属性(__module__、__name__、__qualname__、__doc__、__annotations__),并新增 __wrapped__ 属性,且可直接作为普通函数调用(无需通过类或实例)。 接收 cls(类本身)而非 self(实例),可在不创建实例的情况下访问类变量。 Python 3.10+ 起类方法继承原函数的属性(__module__、__name__、__qualname__、__doc__、__annota

分享

官方文档:https://docs.python.org/zh-cn/3/reference/compound_stmts.html#function-definitions
适用版本:Python 3.10+(2026-05-07 核实)


一、函数参数详解

参数类型全览

def func(pos1, pos2, /, normal, *, kw_only1, kw_only2=10, **kwargs):
    pass
符号 含义
pos1, pos2, / / 之前:仅位置参数(Python 3.8+),不能用关键字传入
normal 普通参数,可位置可关键字
* * 之后:仅关键字参数,必须用关键字传入
*args 收集多余位置参数为元组
**kwargs 收集多余关键字参数为字典
# 仅位置参数(/)
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

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)

默认参数陷阱

# 错误:可变默认值是共享的
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

二、装饰器

基础装饰器

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)

带参数的装饰器

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):
    ...

类装饰器

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)

装饰类的方法

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

多个装饰器叠加

@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(见 内置函数完全参考

@staticmethod

不接收 selfcls,等同于普通函数,只是命名空间归类在类里。

Python 3.10+ 起静态方法继承原函数的属性(__module____name____qualname____doc____annotations__),并新增 __wrapped__ 属性,且可直接作为普通函数调用(无需通过类或实例)。

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)。

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 时,不同类型的相同参数(如 11.0)视为不同缓存键
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=Nonelru_cache,更轻量)。

@functools.cached_property (Python 3.8+)

计算一次后缓存到实例 __dict__,后续访问直接读属性(比 @property + lru_cache 更简洁)。

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

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

四、闭包

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

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
# 陷阱:循环变量捕获
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)

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

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 支持:可预填充任意位置的参数,不限于前导位置。

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)

累积计算:

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

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

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')
    )

异步装饰器

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):
    ...

异步上下文管理器

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(...)

七、实用技巧

函数签名检查

import inspect

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

__slots__ 节省内存

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.UnionX | Y 联合类型注册。

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 函数的属性,导致文档工具和调试信息失真。

import functools

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

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

装饰器需要带参数时,使用三层嵌套函数(装饰器工厂):外层函数接收参数,中层返回真正的装饰器,内层是 wrapper,逻辑清晰。

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

# 错误:所有函数都打印 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,函数体内创建新对象。

# 错误
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)


参见

阅读更多

Web 安全基础

1. HTML 转义(服务端渲染必须): 2. CSP(Content Security Policy): 3. HttpOnly Cookie:防止 JS 读取会话 Cookie: 4. 前端框架防护: 攻击者在第三方网站构造一个表单,诱导已登录用户提交,浏览器会自动携带目标站的 Cookie。 触发条件: 1. 用户已登录目标网站(Cookie 有效) 2. 目标 API 仅凭 Cookie 识别用户身份 3. 请求来源未验证 1. CSRF Token(推荐): 2. SameSite Cookie: 3. 验证 Origin/Referer 头:

By yellowdog

HTTP 协议深度指南

HTTP(HyperText Transfer Protocol)是 Web 的基础传输协议,基于 TCP/IP,采用请求/响应模型。 相关文档:Web安全基础(/web-an-quan-ji-chu/) FastAPI完全指南(/fastapi-wan-quan-zhi-nan/) Nginx完全指南(/nginx-wan-quan-zhi-nan/) 幂等性:多次执行相同请求,服务器状态结果相同。PUT /users/1 多次执行结果一致;POST /users 每次创建新资源,非幂等。 浏览器直接从本地缓存读取,不向服务器发送请求。 缓存命中时,状

By yellowdog

系统设计基础

SLA 对照表: 选择建议:无状态服务(Web 层、API 层)优先水平扩展;数据库初期垂直扩展,达到瓶颈后考虑分库分表或读写分离。 缓存穿透(查询不存在的 key,每次都打到 DB): 缓存击穿(热点 key 过期,瞬间大量请求打到 DB): 缓存雪崩(大量 key 同时过期,或缓存服务宕机): 令牌桶 Python 实现: Redis 实现分布式限流(滑动窗口): URL 命名规则: Cursor 分页响应格式: 雪花算法结构(64 bit): 定义:分布式系统不能同时满足以下三个特性: 在分布式环境中 P 是必须保证的,所以实际是 CP vs AP

By yellowdog

算法思路与模板

二分查找要求序列有序,每次将搜索范围缩减一半,时间复杂度 O(log n)。 两个指针从两端向中间收缩,常用于有序数组。 滑动窗口维护一个满足条件的区间 left, right,right 不断向右扩张,条件不满足时收缩 left。 滑动窗口通用框架: 1. 确定"子问题":原问题可以分解为哪些规模更小的同类问题 2. 定义 dpi 或 dpij 的含义,要足够清晰 3. 推导状态转移方程 4. 确定初始状态(边界条件) 5. 确定计算顺序(确保依赖的子问题先计算) 每件物品最多选一次。dpj = 容量为 j 时的最大价值,逆序遍历容量防止重复选取。 每

By yellowdog