functools.update_wrapper() will not accept descriptor (decorator) as wrapper
まだ誰も着手していません。
- 主要言語
- Python
- スター
- 5.1k
- フォーク
- 2.1k
- 平均マージ
- 1日 19時間
- マージ済み PR(30日)
- 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.
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
調査の方向性
stdlib/functools.pyi の82~87行目付近から始め、wrapper のアノテーションと、リンク先の lazyproperty.py gist における descriptor の使用方法を比較します。lazyproperty.py に対して pyright v1.1.296 を実行してエラーを再現し、その後、参照されている typeshed のコミットと以前の pull request を調べて背景を確認します。関連のない update_wrapper の型付けを弱めることなく、正当な descriptor デコレーターが型チェックを通過すれば完了です。
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- python
- 領域
- developer-experience, tooling
- issue の種類
- バグ
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 活発さ
- 停滞
- 明瞭さ
- おおむね明確
- 初心者へのやさしさ
- 35/100