Skip to main content

python-decorator

负责《Paddle API 对齐 PyTorch 项目》中 Step2 代码修改,实施『Python 装饰器』方案。通过 Python 装饰器,在 Python 层为 Paddle API 实现参数名称、参数顺序、参数类型和参数用法的重载,实现 PyTorch 风格的 API 调用,并保持 Paddle API 的向后兼容性。

Source facts

Repository
PaddlePaddle/docs
Last source activity
June 26, 2026 at 08:08
Detected SKILL.md language
Chinese
Stars
284
Forks
904

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
python-decorator
description
负责《Paddle API 对齐 PyTorch 项目》中 Step2 代码修改,实施『Python 装饰器』方案。通过 Python 装饰器,在 Python 层为 Paddle API 实现参数名称、参数顺序、参数类型和参数用法的重载,实现 PyTorch 风格的 API 调用,并保持 Paddle API 的向后兼容性。
context
fork
disable-model-invocation
false
# 一、现有装饰器体系 Paddle 现有装饰器统一位于 `${ROOT_DIR}/Paddle/python/paddle/utils/decorator_utils.py`,按功能分为两类: ## 1.1 通用别名装饰器 | 装饰器 | 支持参数数量 | 性能 | 使用场景 | 优先级 | |---------|-------------|------|----------|--------| | `param_one_alias` | 1 个 | 高 | 单个参数别名(如 x↔input) | ⭐⭐⭐ | | `param_two_alias` | 2 个 | 高 | 两个参数别名(如 x↔input, axis↔dim) | ⭐⭐⭐ | | `ParamAliasDecorator` | 3 个及以上 | 中 | 复杂参数映射场景 | ⭐⭐ | | `param_two_alias_one_default` | 2 个+默认值 | 低 | 需要默认值(median 专用) | ⭐(不推荐)| ## 1.2 专用装饰器 | 装饰器 | 功能描述 | 关键特性 | |--------|----------|----------| | `index_select_decorator` | 参数别名 + 参数顺序转换 | 检测第 2 个位置参数是否为 int 来判断不同参数顺序 | | `index_add_decorator` | 参数别名 + 参数顺序转换 | 检测第 2 个位置参数是否为 int 来判断不同参数顺序 | | `transpose_decorator` | 参数别名 + 参数用法转换 | 通过 dim0/dim1 两个 int 来自动构造 perm 列表 | | `size_args_decorator` | 参数别名 + 可变参数 | 合并全部 int 位置参数为 shape 列表 | | `view_decorator` | 参数别名 + 可变参数 | 合并全部 int 位置参数为 shape 列表 | | `reshape_decorator` | 参数别名 + 可变参数 | 第 1 个参数别名,合并后面多个 int 位置参数为 shape 列表 | | `expand_decorator` | 参数别名 + 可变参数 | 第 1 个参数别名,合并后面多个 int 位置参数为 shape 列表 | | `legacy_reduction_decorator` | 参数别名 + 报错信息增强 | 检测到 size_average/reduce 用法后,增强报错信息 | | `lp_pool_function_decorator` | 参数别名 + 参数顺序转换 | 检测第 5 个位置参数是否为 bool 来判断不同参数顺序 | # 二、标准工作流程 **整体流程**:Step 1 差异分析与选择装饰器 → Step 2 应用或开发装饰器 → Step 3 更新函数文档 ## Step 1: 差异分析与选择装饰器 根据 PyTorch API 与 Paddle API 的**差异分析**来区分不同场景,选择合适的装饰器方案。 --- ### 场景选择表 | 差异类型 | 参数顺序 | 参数个数 | 参数用法 | 推荐方案 | |---------|--------|--------|--------|--------| | 仅参数名不同 | 相同 | 相同 | 相同 | ✅ 方式一(通用别名装饰器) | | 参数名+参数顺序不同 | 不同 | 相同 | 相同 | ❌ 需要使用方式二(专用装饰器) | | 参数名+参数个数不同 | 相同 | 不同 | 相同 | ❌ 需要使用方式二(专用装饰器) | | 参数名+参数用法不同 | 相同 | 相同 | 不同 | ❌ 需要使用方式二(专用装饰器) | | 其他复杂情况 | 其他 | 其他 | 其他 | ❌ 需要使用方式二(专用装饰器) | **判断方法**:若上表中任何一列出现"不同",则需使用方式二开发专用装饰器。 --- ### 1. 仅参数名不同(参数顺序相同) 根据需要映射的参数数量,选择对应的通用别名装饰器: - 1 个参数 → `param_one_alias` - 2 个参数 → `param_two_alias` - 3 个及以上参数 → `ParamAliasDecorator` **使用示例**: ```python # 单个参数别名 ## torch.deg2rad(input) ## paddle.deg2rad(x) from paddle.utils.decorator_utils import param_one_alias @param_one_alias(['x', 'input']) def deg2rad(x, name=None, *, out=None): ... # 两个参数别名 ## torch.squeeze(input, dim) ## paddle.squeeze(x, axis) from paddle.utils.decorator_utils import param_two_alias @param_two_alias(["x", "input"], ["axis", "dim"]) def squeeze(x, axis=None, name=None): ... # 多个参数别名 ## torch.nn.functional.normalize(input, p, dim, eps) ## paddle.nn.functional.normalize(x, p, axis, epsilon) from paddle.utils.decorator_utils import ParamAliasDecorator @ParamAliasDecorator({"x": ["input"], "axis": ["dim"], "epsilon": ["eps"]}) def normalize(x, p=2, axis=1, epsilon=1e-12, out=None, name=None): ... ``` ### 2. 参数名不同 + 可变参数 开发新的专用装饰器,可参考`size_args_decorator`、`reshape_decorator`、`expand_decorator`、`view_decorator` **使用示例**: ```python # torch.reshape(x, 2, 5) # shape 支持可变参数用法 # paddle.reshape(x, [2, 5]) from paddle.utils.decorator_utils import reshape_decorator @reshape_decorator() def reshape(x, shape, name=None): ... ``` ### 3. 参数名不同 + 参数顺序不同 开发新的专用装饰器 **示例**: ```python # torch.index_select(input, dim, index) # paddle.index_select(x, index, axis) from paddle.utils.decorator_utils import index_select_decorator @index_select_decorator() def index_select(x, index, axis=0, *, out=None): ... ``` ### 4. 参数名不同 + 参数用法不同 开发新的专用装饰器 **示例**: ```python # torch.transpose(x, dim0, dim1) # 交换两个维度, # paddle.transpose(x, perm) from paddle.utils.decorator_utils import transpose_decorator @transpose_decorator() def transpose(x, perm=None, name=None): ... ``` ## Step 2: 应用或开发装饰器 根据**Step1**中的不同场景: - **场景 1**(仅参数名不同):请进入 **方式一(通用别名装饰器)**。 - **场景 2 & 3 & 4**(可变参数、参数顺序不同、参数用法不同及其他情况):请进入 **方式二(开发新的专用装饰器)**。 ### 方式一:通用别名装饰器 1. 在 API 函数文件中导入装饰器 2. 在 API 函数定义前添加装饰器 **示例**: ```python from paddle.utils.decorator_utils import param_two_alias @param_two_alias(["x", "input"], ["axis", "dim"]) def cumsum(x, axis=None, dtype=None, name=None): ... ``` ### 方式二:开发新的专用装饰器 1. 在`${ROOT_DIR}/Paddle/python/paddle/utils/decorator_utils.py`中定义新装饰器 2. 按照以下模板和要点实现 3. 在 API 函数上使用新装饰器 **装饰器模板**: ```python import functools import inspect def custom_decorator(): """ 装饰器功能说明 Usage Example: PyTorch: torch.api(arg1, arg2) Paddle: paddle.api(arg2, arg1) # 或其他映射关系 """ def decorator(func): @functools.wraps(func) # 保持原函数的__name__, __doc__等元信息 def wrapper(*args, **kwargs): # 1. 参数别名映射 if "input" in kwargs and "x" not in kwargs: kwargs["x"] = kwargs.pop("input") # 2. 参数顺序转换或其他特殊处理 # 根据需要调整 args # 3. 调用原函数 return func(*args, **kwargs) wrapper.__signature__ = inspect.signature(func) # 保持函数签名 return wrapper return decorator ``` **关键实现要点**: 1. **关键字参数别名** ```python if "input" in kwargs and "x" not in kwargs: kwargs["x"] = kwargs.pop("input") ``` 2. **位置参数类型检测**(用于判断参数顺序) ```python if len(args) >= 2 and isinstance(args[1], int): # 检测 args 第 2 个值是否为 int,从而判断是 torch 用法还是 paddle 用法,并根据不同的 args 顺序统一匹配为 kwargs ## torch.index_select(input, dim, index) ## paddle.index_select(x, index, axis) kwargs["x"] = args[0] kwargs["axis"] = args[1] args = args[2:] ``` 3. **可变参数处理** ```python # 合并多个 int 位置参数为列表 if len(args) >= 2 and all(isinstance(arg, int) for arg in args[1:]): kwargs["shape"] = list(args[1:]) args = args[:1] ``` **完整示例**: ```python def index_select_decorator(): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): # 1. 参数别名映射 if "input" in kwargs and "x" not in kwargs: kwargs["x"] = kwargs.pop("input") if "dim" in kwargs and "axis" not in kwargs: kwargs["axis"] = kwargs.pop("dim") # 2. PyTorch 参数顺序匹配:识别不同的 args 顺序,统一处理为 kwargs if len(args) >= 2 and isinstance(args[1], int): # PyTorch 顺序: (input, dim, index) → Paddle 顺序: (x, index, axis) kwargs["x"] = args[0] kwargs["axis"] = args[1] if len(args) > 2: kwargs["index"] = args[2] args = () return func(*args, **kwargs) wrapper.__signature__ = inspect.signature(func) return wrapper return decorator ``` **注意事项**: 1. 尽可能参考 `${ROOT_DIR}/Paddle/python/paddle/utils/decorator_utils.py` 中已有的专用装饰器来实现,在风格和逻辑上保持尽可能一致 2. 如果两者 API 对应参数的顺序不同,则装饰器需要通过位置参数(args)类型检测来区分两者,并分别匹配不同的参数顺序 3. 专用装饰器应该尽可能逻辑简单(单个函数控制在 30 行以内),只假定存在 Paddle 签名+PyTorch 签名两种用法,其他情况无需判断,提升性能 4. overload 注解:专用装饰器需添加 overload 注解(通用别名装饰器无需注解),需针对 Paddle 签名、PyTorch 签名分别添加 overload 注解(Paddle 在前,PyTorch 在后)。导入方式:`from typing import overload` ```python @overload def gather( x: Tensor, index: Tensor, axis: Tensor | int | None = None, name: str | None = None, out: Tensor | None = None, ) -> Tensor: ... @overload def gather( input: Tensor, dim: int, index: Tensor, out: Tensor | None = None, ) -> Tensor: ... ``` > **注意**:`out` 参数由方案 3(修改原有 API)负责,本方案不处理 out 参数的新增。 ## Step 3: 更新函数文档字符串 如果使用的是通用别名装饰器,则在文档的 Args 部分为有别名的参数添加 Alias Support 说明,如下: > 注:Alias 说明应放在该参数描述的末尾,格式为: Alias: ``alias_name`` ,多个 Alias 描述为: Alias: ``alias_name1`` or ``alias_name2`` ```python @param_two_alias(["x", "input"], ["axis", "dim"]) def fft( x: Tensor, n: int | None = None, axis: int = -1, norm: _NormalizeMode = "backward", name: str | None = None, *, out: Tensor | None = None, ) -> Tensor: """ Calculate one-dimensional discrete Fourier transform. Args: x (Tensor): The input data. It's a Tensor type. It's a complex. Alias: ``input``. n (int|None, optional): The length of the output transform axis. axis (int, optional): Axis used to calculate FFT. Alias: ``dim``. norm (str, optional): Indicates which direction to scale the `forward` or `backward` transform pair and what normalization factor to use. name (str|None, optional): The default value is None. Keyword args: out(Tensor, optional): The output tensor. """ ``` 如果使用的是专用装饰器,则表明 API 支持了签名重载,需要分别描述两种签名,可以参考代码中的@overload 注解,如下: > 注:只需在文档正文中阐述两种签名(Paddle 在前,Pytorch 在后),文档其他位置如 Args/Returns 仍以 Paddle 风格签名为准 ```python @overload def broadcast_tensors(input: Sequence[Tensor], name: str | None = None) -> list[Tensor]: ... @overload def broadcast_tensors(*tensors: Tensor) -> list[Tensor]: ... @variadic_tensor_decorator('input') def broadcast_tensors(input: Sequence[Tensor], name: str | None = None) -> list[Tensor]: """ Note: This API has two signatures: 1. ``paddle.broadcast_tensors(input, name=None)`` (Paddle-style): Broadcast a list of tensors following broadcast semantics. 2. ``paddle.broadcast_tensors(*tensors)`` (PyTorch-style): Broadcast variadic tensor arguments following broadcast semantics. Args: ... Returns: ... """ ``` 如果支持了 out 参数,必须在 API 文档中描述 out 参数,out 为 keyword-only 参数(*后面)时注意增加`Keyword Args:`,并在此部分描述。out 为位置参数时直接在 Args 部分描述。如下: ```python # out 为 keyword-only 参数 def func(x, name=None, *, out=None): """ ... Args: ... Keyword Args: out (Tensor|optional): The output tensor. Default: None. Returns: ... """ ``` **注意事项**: - Tensor 类方法(如 paddle.Tensor.abs)没有文档,无需处理,请勿与普通方法(如 paddle.abs)混淆 - Inplace 方法(如 paddle.abs_等下划线 API),只需要更新 API 签名,不需要修改文档 # 三、背景知识 ## 3.1 Paddle API 分层结构 **Paddle API 架构(5 层)**: 1. **Python 层**:Python 函数定义(本方案修改层) 2. **Pybind 层**:Python 与 C++绑定(自动生成) 3. **Dygraph 层**:动态图前反向传播组合(自动生成) 4. **C++ API 层**:Kernel 选择调度(自动生成) 5. **Kernel 层**:实际计算逻辑实现(C++) **本方案修改范围**: - ✅ 仅修改第 1 层(Python 层) - ❌ 第 2~4 层通过 yaml 配置自动生成,无需手动修改 - ❌ 第 5 层涉及 C++实现,不在本方案范围内 ## 3.2 Python 装饰器原理 ### 装饰器基本结构 ```python import functools import inspect def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): # 参数处理逻辑 return func(*args, **kwargs)
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub