You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python:functools.wraps能否用于非装饰器函数?lru_cache类型提示修复

问题描述

我有如下函数:

def load_resources(path: Path, src_type: str, *, load_all: bool = False) -> bool:
    ... # 此处执行逻辑并返回值

在VSCode中调用该函数时,能看到完整的类型提示:

(function) def load_resources(path: Path, src_type: str, *, load_all: bool = False) -> bool

由于该函数调用耗时,我想用functools.lru_cache装饰它(Python3.8环境):

@functools.lru_cache()
def load_resources(path: Path, src_type: str, *, load_all: bool = False) -> bool:
    ...

但装饰后,原有类型提示被覆盖,变成了:

(function) load_resources: _lru_cache_wrapper[Unknown]

于是我尝试用functools.wraps包装:

def __load_resources(path: Path, src_type: str, *, load_all: bool = False) -> bool:
    ...

@functools.wraps(__load_resources)
@functools.lru_cache()
def load_resources(*args, **kwargs):
    return __load_resources(*args, **kwargs)

却得到了奇怪的类型提示:

(function) load_resources: _Wrapped[(__load_resources(path: Path, src_type: str, *, load_all: bool = False), bool, (*args: Hashable, **kwargs: Hashable), Unknown]

我知道__name__元数据有问题,但原以为其他特性会正常生效。想问两个问题:

  1. 非装饰器场景下使用functools.wraps是否错误?
  2. 如何为被functools.lru_cache装饰的函数添加正确的类型提示?

我的临时实现方式如下:

def load_resources(path: Path, src_type: str, *, load_all: bool = False) -> bool:
    return __get_context(path, src_type, load_all=load_all)

解决方案

1. 非装饰器场景用functools.wraps是否错误?

不是错误,但你的用法有问题。functools.wraps的核心作用是复制原函数的元数据(比如__name__、__doc__、__annotations__等),但它不负责类型签名的推断。你之前把load_resources定义成*args, **kwargs的签名,和原函数__load_resources的精确签名不匹配,类型检查器无法从可变参数关联到原函数的参数类型,所以才出现混乱的提示。

如果一定要用wraps,需要保证包装函数的签名和原函数完全一致,而不是用可变参数:

from functools import lru_cache, wraps
from pathlib import Path

def __load_resources(path: Path, src_type: str, *, load_all: bool = False) -> bool:
    ...

@wraps(__load_resources)
@lru_cache()
def load_resources(path: Path, src_type: str, *, load_all: bool = False) -> bool:
    return __load_resources(path, src_type, load_all=load_all)

这样既保留了缓存功能,wraps也能正确复制元数据,类型检查器也能识别到正确的签名。

2. 为lru_cache装饰的函数添加正确类型提示的方法

针对Python3.8,推荐两种简洁可靠的方式:

方式一:对外暴露精确签名的函数,内部调用带缓存的私有函数

这和你的临时实现思路一致,只是把私有函数加上lru_cache装饰,对外的函数保持原有的精确签名:

from functools import lru_cache
from pathlib import Path

@lru_cache()
def __load_resources(path: Path, src_type: str, *, load_all: bool = False) -> bool:
    # 原函数逻辑
    ...

def load_resources(path: Path, src_type: str, *, load_all: bool = False) -> bool:
    return __load_resources(path, src_type, load_all=load_all)

这种方式最直观,类型检查器能直接识别load_resources的完整签名,同时私有函数的缓存功能正常生效,完全避免了装饰器导致的类型提示丢失问题。

方式二:用typing.cast结合Protocol强制指定类型(适合不想拆分函数的场景)

如果不想拆分函数,可以用typing.Protocol定义和原函数一致的签名,再通过cast告诉类型检查器装饰后的函数符合该签名:

from functools import lru_cache
from pathlib import Path
from typing import cast, Protocol

# 定义匹配原函数签名的Protocol
class LoadResourcesProtocol(Protocol):
    def __call__(self, path: Path, src_type: str, *, load_all: bool = False) -> bool:
        ...

@lru_cache()
def load_resources(path: Path, src_type: str, *, load_all: bool = False) -> bool:
    # 原函数逻辑
    ...

# 强制转换类型,让类型检查器识别正确的签名
load_resources = cast(LoadResourcesProtocol, load_resources)

这种方式不需要拆分函数,通过显式的类型声明让类型检查器忽略lru_cache包装器的干扰,识别到原函数的签名。


内容的提问来源于stack exchange,提问作者jolim

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 17:23:11