Mypy typehint可能是一种采用`cls'参数而不是`self'的方法吗?

发布于 2025-02-03 20:42:07 字数 830 浏览 3 评论 0 原文

我很好奇是否有可能让Mypy了解类中的某些方法 cls 参数而不是 self 参数。我想到的具体用例是在键入sqlalchemy hybrid_property 's .expression decorator,但更简单的示例将尝试实现自定义/非构建 - 在 classMethod 的版本中。

将显式类型注释应用于 cls 不起作用,例如

from typing import Type

class Example:
  @my_custom_classmethod
  def foo(cls: Type["Example"]) -> str:
    return "doesn't work"

mypy抱怨删除的自我“ type [ackemens]”不是其类“示例” ,我猜这通常是有道理的,但是在这里没有意义:/

我设法提出的最好的解决方案就是##type:imagore 方法的签名:

class Example:
  @my_custom_classmethod
  def foo(cls: Type["Example"]) -> str: # type: ignore
    return "this actually kind of does work!"

实际上,这实际上确实可以正常工作,因为 cls 在方法的主体中具有正确的类型,并且装饰器接收到正确类型的方法,但感觉有些骇人听闻。

I'm curious if it's possible to get mypy to understand that some method in a class takes a cls argument rather than a self argument. The concrete use-case I have in mind is typing a sqlalchemy hybrid_property's .expression decorator, but a simpler example would be trying to implement a custom/non-built-in version of classmethod.

Applying an explicit type annotation to cls doesn't work, e.g.

from typing import Type

class Example:
  @my_custom_classmethod
  def foo(cls: Type["Example"]) -> str:
    return "doesn't work"

Mypy complains that The erased type of self "Type[Example]" is not a supertype of its class "Example", which, ok, I guess often makes sense, but it doesn't make sense here :/

The best solution I've managed to come up with so far is to just # type: ignore the method's signature:

class Example:
  @my_custom_classmethod
  def foo(cls: Type["Example"]) -> str: # type: ignore
    return "this actually kind of does work!"

This actually does seem to basically work, insofar as cls has the right type in the body of the method and the decorator receives a method of the right type, but it feels a bit hacky.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

时间海 2025-02-10 20:42:07

bind_self 是一种检查和分配 self cls )类型的方法。它依赖于 is_classmethod node flag(实现在这里)。

flag is_classmethod 在语义分析期间设置在这里。它是通过直接比较装饰器和 incears.classmethod 来完成的。因此,您的 custom_classmethod 不被视为 classMethod

因此,您无法生产将相当于 ClassMethod 内置的装饰器。但是,您可以使用自定义描述符克服此问题:

from __future__ import annotations
from typing import Callable, Concatenate, Generic, ParamSpec, TypeVar, TYPE_CHECKING

_T = TypeVar('_T')
_R = TypeVar('_R')
_P = ParamSpec('_P')


if TYPE_CHECKING:
    base = classmethod
else:
    class base(Generic[_R], classmethod):
        pass
    
class my_classmethod(base[_R], Generic[_P, _R]):
    @property
    def __func__(self) -> Callable[Concatenate[_T, _P], _R]:
        return super().__func__
    def __init__(self: my_classmethod[_P, _R], f: Callable[Concatenate[_T, _P], _R], /) -> None:
        super().__init__(f)
    def __get__(self, obj: _T, type: type[_T] | None = None) -> Callable[_P, _R]:
        return super().__get__(obj, type)
        
    
class A:
    @my_classmethod
    def foo(cls, x: int) -> str:
        return str(x)

reveal_type(A.foo(1))  # N: Revealed type is "builtins.str"
reveal_type(A().foo(1))  # N: Revealed type is "builtins.str"
A.foo('a')  # E: Argument 1 has incompatible type "str"; expected "int"

这在运行时起作用并通过类型检查。描述符很有趣。我假设 python> = 3.10 ,最近的足够 mypy :较旧的Python版本可能需要一些更改(至少从 typing_extensions 导入),旧的 mypy 在完整的描述符支持的情况下可能会遇到麻烦。是的,您可以从 ClassMethod 继承。如果您不想要,则必须重新实施 __获取__ __ INIT __ 无论如何(以及 __ func __ 属性,如果您喜欢一致性)。请记住,在3​​.10及以后的 classMethod 还具有包装属性。

这是

bind_self is a method that checks and assigns self (cls) type. It relies on is_classmethod node flag (implementation here).

Flag is_classmethod is set during semantic analysis here. It is done by direct comparison of decorator and builtins.classmethod. So your custom_classmethod is not considered classmethod.

So you cannot produce decorator that will be equivalent to classmethod builtin. However, you can overcome this problem using custom descriptor:

from __future__ import annotations
from typing import Callable, Concatenate, Generic, ParamSpec, TypeVar, TYPE_CHECKING

_T = TypeVar('_T')
_R = TypeVar('_R')
_P = ParamSpec('_P')


if TYPE_CHECKING:
    base = classmethod
else:
    class base(Generic[_R], classmethod):
        pass
    
class my_classmethod(base[_R], Generic[_P, _R]):
    @property
    def __func__(self) -> Callable[Concatenate[_T, _P], _R]:
        return super().__func__
    def __init__(self: my_classmethod[_P, _R], f: Callable[Concatenate[_T, _P], _R], /) -> None:
        super().__init__(f)
    def __get__(self, obj: _T, type: type[_T] | None = None) -> Callable[_P, _R]:
        return super().__get__(obj, type)
        
    
class A:
    @my_classmethod
    def foo(cls, x: int) -> str:
        return str(x)

reveal_type(A.foo(1))  # N: Revealed type is "builtins.str"
reveal_type(A().foo(1))  # N: Revealed type is "builtins.str"
A.foo('a')  # E: Argument 1 has incompatible type "str"; expected "int"

This works on runtime and passes type checking. Descriptors are funny. I'm assuming python >= 3.10 and recent enough mypy: older python versions may require some changes (at least imports from typing_extensions), old mypy can have troubles with full descriptor support. And yes, you can inherit from classmethod. If you don't want, you'll have to re-implement __get__ and __init__ anyway (and __func__ property if you like consistency). Remember that in 3.10 and later classmethod also has wrapped property.

Here's playground with this code.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文