Skip to main content

python-decorator

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

소스 정보

저장소
PaddlePaddle/docs
최근 소스 활동
2026년 6월 26일 08:08
감지된 SKILL.md 언어
중국어
스타
284
포크
904

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
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)
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기