Paddle
                                
                                 Paddle copied to clipboard
                                
                                    Paddle copied to clipboard
                            
                            
                            
                        【WIP】【Type Hints】Paddle 中引入 Tensor stub 和 Paddle/python/paddle/py.typed 文件
PR Category
User Experience
PR Types
Others
Description
关联 PR : https://github.com/PaddlePaddle/Paddle/issues/63597
任务:1-2
根据 https://github.com/PaddlePaddle/Paddle/pull/50211 生成 tensor.pyi
涉及文件:
- .pre-commit-config.yamlpre-commit 不检查 tensor.pyi
- python/CMakeLists.txt编译后执行 gen_tensor_stub.py
- python/paddle/__init__.pytype checking 时引入 tensor 目录下的 Tensor
- python/paddle/py.typed标记文件
- python/setup.py.in增加 py.typed 和 tensor.pyi
- setup.py增加 py.typed 和 tensor.pyi
- tools/gen_tensor_stub.py生成 tensor.pyi
目前 (20240428) 还有几个问题:
- 编译时的命令,gen_tensor_stub.py加在哪里合适?python/CMakeLists.txt吗?有这方面规划吗?
- python/setup.py.in和- setup.py都需要增加 py.typed 和 tensor.pyi ?
- gen_tensor_stub.py目前只是把 https://github.com/PaddlePaddle/Paddle/pull/50211 里面的拿过来,还需要优化。
你的PR提交成功,感谢你对开源项目的贡献! 请关注后续CI自动化测试结果,详情请参考Paddle-CI手册。 Your PR has been submitted. Thanks for your contribution! Please wait for the result of CI firstly. See Paddle CI Manual for details.
Update 20240505
gen_tensor_stub.py 利用模板 tensor.pyi.in 生成 tensor.pyi ~
这里参考 https://github.com/PaddlePaddle/Paddle/pull/50211 中的方法,主要不同:
- 
利用模板,而不是直接生成 stub 文件 主要逻辑为:在模板中插入 api (signature & docstring),或 api 的 docstring (仅有 signature 没有 docstring 的 api),如果 get_tensor_members的名称与模板中相同,则优先使用模板中的 signature 和 docstring。主要原因为:单使用代码生成 stub 文件,有些地方比较难处理,比如 c++ 中的一些接口,只有简单的方法名称,如果要添加 type 或者 docstring 会比较麻烦,或者比如,有些方法需要 overload (如 init),使用模板也会更简单。 后续,可以不断完善 tensor.pyi.in 文件,c++ 中不方便修改的,添加到模板中即可,模板的优先级高于 get_tensor_members。另外,目前 tensor.pyi.in 还不完善,有些 api 的签名还没改,后面需要慢慢完善。 
- 
对于 inspect.isdatadescriptor(member),当作@property处理,而不是 attribute ,主要是由于,这些属性大部分有 docstring,如data,如果设置成 attribute 就丢失了 ~
- 
Tensor 的签名改为 class Tensor(Generic[_ShapeType, _DType]),增加两个类型 shape 和 dtype ,如def __eq__(self, y: Tensor) -> Tensor[Any, bool]可以使用,后续也可以方便扩展 ~
另外,没有修改 tensor.py 文件,本来想把 python/paddle/__init__.py 中的 Tensor = framework.core.eager.Tensor 移入 tensor.py 里面,结果发现,目前的源码中有的地方用到了 paddle.Tensor 作为 type annotation,如果移入 tensor.py ,会出现 circular import ,为保险起见做如下处理
if typing.TYPE_CHECKING:
    from .tensor.tensor import Tensor
else:
    Tensor = framework.core.eager.Tensor
    Tensor.__qualname__ = 'Tensor'
python/paddle/tensor/tensor.pyi 为生成的 stub 文件,可供参考 ~
目前,paddle.Tensor 中共有 550 个成员,其中:
- 添加 478个方法
- 添加 10个 alias
- 添加 1个 alias__qualname__ = "Tensor"
- 不添加 11个is_inherited_member
- 不添加 48个私有成员
- 不添加 3个特殊成员__array_ufunc__、__module__、__new__
以上共计 551 个 (不含 overload)~
@SigureMo 请评审 ~
Sorry to inform you that 770c25b's CIs have passed for more than 7 days. To prevent PR conflicts, you need to re-run all CIs manually.
利用模板,而不是直接生成 stub 文件
模板唯一一个问题是不受 Ruff 等工具监控,代码风格无法保证
Tensor 的签名改为
class Tensor(Generic[_ShapeType, _DType]),增加两个类型 shape 和 dtype
我的建议是在想好 API 形态前不要加泛型,一旦现在加了,以后改就是 breaking change,但从非泛型到泛型在非 strict mode 下是兼容性变动
如 def eq(self, y: Tensor) -> Tensor[Any, bool] 可以使用,后续也可以方便扩展 ~
而且这个签名也不对啊,dtype 哪来的 bool
利用模板,而不是直接生成 stub 文件
模板唯一一个问题是不受 Ruff 等工具监控,代码风格无法保证
是的,所以这个模板文件原则上应该是能通过 mypy 的 python 文件 ~ 而不是 torch 的那种混有 ${xxx} 进行字符串插入的模板 ~
我在线下用 mypy 检查是通过的,后面考虑是否把他也加到 CI 的检查里面?
Tensor 的签名改为
class Tensor(Generic[_ShapeType, _DType]),增加两个类型 shape 和 dtype我的建议是在想好 API 形态前不要加泛型,一旦现在加了,以后改就是 breaking change,但从非泛型到泛型在非 strict mode 下是兼容性变动
如 def eq(self, y: Tensor) -> Tensor[Any, bool] 可以使用,后续也可以方便扩展 ~
而且这个签名也不对啊,dtype 哪来的 bool
本来是没加泛型的,结果就是因为这个 eq 和 nq 方法,返回的是个 bool 类型的 Tensor ,如果只写 Tensor 感觉语义不明确,所以就加上了 ~ 其他地方暂时没看到需要泛型的,要么就去掉吧?
另外,dtype 有 bool 啊
In [2]: a = paddle.to_tensor(False)
In [3]: a
Out[3]: 
Tensor(shape=[], dtype=bool, place=Place(gpu:0), stop_gradient=True,
       False)
为啥没有???
补充说明一下根据模板 tensor.pyi.in 生成 tensor.pyi 的逻辑:
- 利用正则在 # annotation: ${xxx}后面插入 docstring、methods 等
- 利用正则在 def xxx()...方法中插入文档
- 模板中存在定义的方法,如果缺少文档,则只插入文档
另外,关于 tensor.pyi.in 这个模板文件的维护问题,我的想法是,不需要特殊维护,因为,后面 CI 会检查 api 的 typing,如果 tensor 接口有变,且没有办法自动生成有效的签名,那么,如果不修改 tensor.pyi.in 文件手动添加,则 CI 检查 可能 会 fail(没有示例代码和类型测试用例,那就没办法检查了 ... ...)。
这个可以在 《Paddle 中的类型提示》 的开发文档里面写明 ~
另外,dtype 有 bool 啊
这是 paddle.bool 吧,但就算写 paddle.bool 也不对,泛型参数是不能写 paddle.bool 这种「值」的,所以需要设计一种方式覆盖这种表达,但明显目前是不具备的
我在线下用 mypy 检查是通过的,后面考虑是否把他也加到 CI 的检查里面?
如果是合法的 pyi 文件,建议使用 .pyi 后缀,天然所有的检查工具都会检查(black、Ruff 等等)
可以考虑改为 tensor.prototype.pyi 之类的
另外,关于 tensor.pyi.in 这个模板文件的维护问题,我的想法是,不需要特殊维护,因为,后面 CI 会检查 api 的 typing
对的,我不关心这个文件自身的 typing 问题,因为我们本就通过叶子结点(API)去检查了这一点,我这里只是说其它的通用检查工具,以及,编辑器并不会给 .in 提供语法高亮
比如 #63256 就是因为不爽很久了,直接改了后缀
抱歉,才发现有几个问题没有回复 ~~~
应该是太多了搜索卡住了,但
__ror__应该是没有的
我看了一下,dir(paddle.Tensor) 里面确实没有 ~ 你这个 __ror__ 是哪来的?
诶?为啥不从源头删掉?这个文件应该不需要 mypy 检查的
tensor.pyi.in 需要检查啊,不然怎么保证开发者修改这个文件的时候没有改出问题?
另外,对比 https://github.com/cattidea/paddlepaddle-stubs/blob/main/paddle-stubs/_typing/tensor.pyi,以下方法不在 paddle.Tensor 中,进而也不在此次生成的 stub 文件中:
__pos__
__rfloordiv__
__rmod__
__rmatmul__
__ror__
__rand__
__rxor__
__lshift__
__rshift__
__rlshift__
__rrshift__
__abs__
__iter__
__contains__
__dlpack__
__dlpack_device__
这些方法怎么来的?需要手动添加吗?
@SigureMo
另外,对比 https://github.com/cattidea/paddlepaddle-stubs/blob/main/paddle-stubs/_typing/tensor.pyi,以下方法不在 paddle.Tensor 中,进而也不在此次生成的 stub 文件中:
包括 __ror__,这些应该是我觉得「应该」存在的方法,不过这里应该和运行时保持一致,这些不存在的就不要加了
tensor.pyi.in 需要检查啊,不然怎么保证开发者修改这个文件的时候没有改出问题?
检查方式有想好么?可以考虑复用 pre-commit,不建议在 CI 单独开逻辑,会和开发工作流割裂,提完 PR 在 CI 上才发现问题,不了解的同学调试起来也很麻烦
包括
__ror__,这些应该是我觉得「应该」存在的方法,不过这里应该和运行时保持一致,这些不存在的就不要加了
OK ~
tensor.pyi.in 需要检查啊,不然怎么保证开发者修改这个文件的时候没有改出问题?
检查方式有想好么?可以考虑复用
pre-commit,不建议在 CI 单独开逻辑,会和开发工作流割裂,提完 PR 在 CI 上才发现问题,不了解的同学调试起来也很麻烦
嗯!已经改为 tensor.prototype.pyi ,pre-commit 可以直接检查 ~~~
另外,做以下修改:
- setup 里面只打包 tensor.pyi,也就是生成的 stub 文件,不打包这个tensor.prototype.pyi
- 去掉了 tensor.prototype.pyi里面的from __future__ import annotations,pre-commit用来检查的话确实就用不到了 ~
另外,为了更好的 stub 检查,可以考虑引入 Ruff 的 flake8-pyi rules 了~(独立任务)
https://docs.astral.sh/ruff/rules/#flake8-pyi-pyi
Update 20240523:
- _typing.basic.py 添加 TensorLike
- core.pyi 添加 Place
- 更新 tensor.prototype.pyi