解决Python装饰器中的TypeVar类型检查错误
解决装饰器类型注解的mypy错误
问题说明
我正在给装饰器加类型注解以通过mypy检查,参考相关规范和帖子后有进展,但碰到了以下错误:
decorators.py:12: error: A function returning TypeVar should receive at least one argument containing the same TypeVar [type-var]
decorators.py:12: note: Consider using the upper bound "Callable[..., Any]" instead
我已经在代码里用了bound=Callable[..., Any],还是搞不定,相关代码如下:
"""Decorator Functions.""" from typing import Any, Callable, List, TypeVar, Union, cast from pandas import DataFrame, concat F = TypeVar('F', bound=Callable[..., Any]) # Decorator to process DataFrame with ignore columns def process_without_columns( ignore_cols: List[str], final_cols_order: Union[List[str], None] = None ) -> F: """ Decorate to process a DataFrame, removing specified ignore columns, and then joining them back. Parameters ---------- ignore_cols: List[str] List of column names to ignore during processing. final_cols_order: Union[List[str], None] List specifying the desired order of columns in the final DataFrame. If None, the original DataFrame's column order will be used. Default is None. Returns ------- decorator_process: Decorator function that processes the DataFrame. """ def decorator_process(func: F) -> F: def inner(self, data_df: DataFrame, *args: Any, **kwargs: Any) -> DataFrame: """ Inner function that performs the actual processing of the DataFrame. Parameters ---------- data_df: DataFrame DataFrame to be processed. *args args passed into inner function **kwargs Kwargs passed into inner function Returns ------- DataFrame: Processed DataFrame with the original columns """ ignore_df = data_df[ ignore_cols ] # Extract the ignore columns as a separate DataFrame data_df = data_df.drop( columns=ignore_cols ) # Remove the ignore columns from the original DataFrame # Process the DataFrame (smaller DataFrame without ignore columns) processsed_df = func(self, data_df, *args, **kwargs) # Join back the processed DataFrame with the ignore columns DataFrame processsed_df = concat([processsed_df, ignore_df], axis=1) # Reorder DataFrame columns if final_cols_order is specified if final_cols_order is not None: processsed_df = processsed_df[final_cols_order] return processsed_df return cast(F, inner) return cast(F, decorator_process)
错误原因
核心问题是process_without_columns的返回类型标注错了:你把它标成了F,但这个函数是带参数的装饰器工厂,它返回的是一个装饰器(也就是接收F类型函数、再返回F类型函数的函数),而不是直接返回F类型的函数。
mypy的报错提示很明确:返回TypeVar的函数必须至少有一个参数包含该TypeVar,否则没法推断这个TypeVar的具体类型。你现在的process_without_columns没有接收F类型的参数,却标注返回F,这就违反了规则。
修复方案
- 修正装饰器工厂的返回类型:把
-> F改成-> Callable[[F], F],明确它返回的是一个“接收被装饰函数、返回同类型函数”的装饰器。 - 优化TypeVar的边界定义:让
F更精确地匹配被装饰函数的签名(因为你的被装饰函数是实例方法,带self参数,接收DataFrame并返回DataFrame)。 - 移除不必要的类型强制转换:原来的
cast(F, decorator_process)是错误的,现在返回类型正确了,不需要这个强制转换。
修复后的代码
"""Decorator Functions.""" from typing import Any, Callable, List, TypeVar, Union, cast from pandas import DataFrame, concat # 限定F为带self、DataFrame参数,返回DataFrame的实例方法类型 F = TypeVar('F', bound=Callable[['Any', DataFrame, *Any, **Any], DataFrame]) def process_without_columns( ignore_cols: List[str], final_cols_order: Union[List[str], None] = None ) -> Callable[[F], F]: # 正确标注返回类型:接收F,返回F的装饰器 """ 装饰器:处理DataFrame时先移除指定列,处理完成后再合并回原列,支持指定最终列顺序。 参数 ---------- ignore_cols: List[str] 处理过程中需要暂时忽略的列名列表 final_cols_order: Union[List[str], None] 最终DataFrame的列顺序,为None则保留原数据列顺序,默认值None 返回 ------- decorator_process: 用于处理DataFrame的装饰器函数 """ def decorator_process(func: F) -> F: def inner(self, data_df: DataFrame, *args: Any, **kwargs: Any) -> DataFrame: """ 执行实际DataFrame处理逻辑的内部函数 参数 ---------- data_df: DataFrame 需要处理的DataFrame对象 *args 传递给被装饰函数的位置参数 **kwargs 传递给被装饰函数的关键字参数 返回 ------- DataFrame: 恢复原忽略列后的处理结果DataFrame """ ignore_df = data_df[ignore_cols] data_df = data_df.drop(columns=ignore_cols) # 调用原函数处理去除忽略列后的DataFrame processed_df = func(self, data_df, *args, **kwargs) # 合并回之前忽略的列 processed_df = concat([processed_df, ignore_df], axis=1) # 如果指定了最终列顺序,重新排列 if final_cols_order is not None: processed_df = processed_df[final_cols_order] return processed_df return cast(F, inner) # 这里的cast是必要的,因为inner的签名和func一致,但mypy需要显式确认 return decorator_process
额外说明
- 优化后的
F边界定义让类型检查更严格,确保被装饰的函数符合实例方法的签名要求。 - 内部的
cast(F, inner)是合理的:因为inner的功能是包装func,签名和返回值类型都和func一致,mypy无法自动推断这一点,所以需要显式转换。
内容的提问来源于stack exchange,提问作者Micah Pearce
相关产品推荐
相关产品推荐

