python / python/typeshed

functools.update_wrapper() will not accept descriptor (decorator) as wrapper

未关闭
#9,846 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

priority: regression
主要语言
Python
星标
5.1k
派生
2.1k
平均合并
1 天 19 小时
30 天内合并 PR
82

描述

Description:

The type stub for functools.update_wrapper() does not permit a descriptor for the wrapper (first) argument, a legitimate case arising when defining a descriptor-type decorator (like @property is).
https://github.com/python/typeshed/blob/main/stdlib/functools.pyi#L83

@erictraut While perhaps not exactly a regression, this used to work up until this commit of yours here (last week): https://github.com/python/typeshed/commit/9c4bfd5d11b47677e128452baa8ac7eeb1903a8e#diff-02c51ed45b741b6765c365fb5d6578c586510bbbd57b10a1dba10aeba37ae157L70-R84. I say not exactly a regression because the prior constraint introduced here was perhaps overly loose: https://github.com/python/typeshed/pull/4627/files

Example
As an example, the following (pyright) typing error is detected in the lazyproperty descriptor-type decorator defined here: https://gist.github.com/scanny/255f16339b74b2b59e896d67bd459d6b

lazyproperty.py|65 col 34-38 error| Argument of type "Self@lazyproperty[T@lazyproperty]"
  cannot be assigned to parameter "wrapper"
  of type "(**_PWrapper@update_wrapper) -> _RWapper@update_wrapper"
  in function "update_wrapper"
 Type "Self@lazyproperty[T@lazyproperty]" cannot be assigned to
 type "(**_PWrapper@update_wrapper) -> _RWapper@update_wrapper"

To Reproduce
The gist referenced above has no dependencies other than the stdlib so can be type-checked directly (using pyright v1.1.296):

$ pyright lazyproperty.py

Analysis

The type definition at work constrains wrapper to be a Callable[_PWrapper, _RWapper]. I believe it also needs to permit Descriptor, although I'm not completely sure what that type definition is. My best guess is something like this:

class HasDunderGet(Protocol):
    def __get__(self, obj: Any, type: type = None) -> Any: ...
class HasDunderSet(Protocol):
    def __set__(self, obj: Any, value: Any) -> None: ...
class HasDunderDelete(Protocol):
    def __delete__(self, obj: Any) -> None: ...

Descriptor = Union[HasDunderGet, HasDunderSet, HasDunderDelete]

Expected behavior
As far as I can tell, this is a legitimate usage of functools.update_wrapper() and decorators that produce a descriptor (like @property does) are also legitimate Python. It has certainly worked for a long time and also even typechecked with pyright previously (v1.1.273), maybe six months ago. So I would expect it to be accepted as written.

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

调研方向

从 stdlib/functools.pyi 的第 82-87 行附近开始,将 wrapper 注解与链接的 lazyproperty.py gist 中对 descriptor 的使用进行比较。对 lazyproperty.py 运行 pyright v1.1.296 以重现错误,然后检查所引用的 typeshed 提交和更早的 pull request 以了解背景。完成标准是:合法的 descriptor 装饰器能够通过类型检查,同时不会削弱无关部分对 update_wrapper 的类型标注。

由索引模型根据 Issue 内容生成。

评估

技术栈
python
领域
developer-experience, tooling
Issue 类型
缺陷
难度
4/5
预计耗时
3-5 天
活跃度
停滞
描述清晰度
基本清楚
新手友好度
35/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。