我有一个类decorator ,它添加了一些函数和字段装饰的类.

@mydecorator
@dataclass
class A:
    a: str = ""

添加(通过setattr())是一个.save()函数和一组数据类字段的信息作为单独的字典.

我希望VScode和mypy能够正确识别这一点,以便在我使用时:

a=A()
a.save()

或这两个被正确识别的a.my_fields_dict个.

有什么办法可以做到这一点吗?也许会在运行时修改类A类型批注?

推荐答案

TL;DR

在当前的类型系统中,您想要做的事情是不可能的.


1.交叉口类型

如果通过修饰器添加到类的属性和方法是静态的(在某种意义上,它们不仅在运行时是已知的),那么您正在描述的实际上是通过混入protocol P来扩展任何给定的类T.该协议定义了方法save等.

要注释这一点,你需要intersection分(满分T & P分).它应该看起来像这样:

from typing import Protocol, TypeVar


T = TypeVar("T")


class P(Protocol):
    @staticmethod
    def bar() -> str: ...


def dec(cls: type[T]) -> type[Intersection[T, P]]:
    setattr(cls, "bar", lambda: "x")
    return cls  # type: ignore[return-value]


@dec
class A:
    @staticmethod
    def foo() -> int:
        return 1

您可能会注意到,Intersection的导入明显丢失了.这是因为,尽管它是用于Python类型系统的most requested features个之一,但到今天为止,它仍然缺失.目前还没有办法在Python类型化中表达这一概念.


2.类装饰者问题

目前唯一的解决办法是在您 Select 的类型判断器的相应插件旁边添加一个自定义实现.我只是偶然发现了typing-protocol-intersection个套餐,它为mypy做了同样的事情.

如果您安装它并在mypy配置中添加plugins = typing_protocol_intersection.mypy_plugin,您可以编写如下代码:

from typing import Protocol, TypeVar

from typing_protocol_intersection import ProtocolIntersection


T = TypeVar("T")


class P(Protocol):
    @staticmethod
    def bar() -> str: ...


def dec(cls: type[T]) -> type[ProtocolIntersection[T, P]]:
    setattr(cls, "bar", lambda: "x")
    return cls  # type: ignore[return-value]


@dec
class A:
    @staticmethod
    def foo() -> int:
        return 1

但现在我们遇到了下一个问题.使用reveal_type(A.bar())mypy进行测试将产生以下结果:

error: "Type[A]" has no attribute "bar"  [attr-defined]
note: Revealed type is "Any"

然而,如果我们这样做:

class A:
    @staticmethod
    def foo() -> int:
        return 1


B = dec(A)

reveal_type(B.bar())

我们没有收到来自mypynote: Revealed type is "builtins.str"的投诉.尽管我们之前所做的是相同的!

这不是插件的错误,而是mypy内部的错误.这是另一个long-standing issue,mypy没有正确地处理类装饰符.

该问题帖子中的一个人甚至将您的用例与所需的交叉点类型结合在一起提及.


DIY

换句话说,你只需要等到这两个洞修补好了.或者,您可以希望至少mypy的装饰符问题很快就会得到解决,同时为交叉点类型编写您自己的VSCode插件.也许你可以和我上面提到的mypy插件背后的人在一起.

Python-3.x相关问答推荐

如何绘制交叉验证的AUROC并找到最佳阈值?

将列表项的极列水平分解为新列

为什么我在BLE中的广告代码在发送包裹之间需要大约1秒

与 pandas 0.22 相比,pandas 2.0.3 中的 df.replace() 会抛出 ValueError 错误

在Pandas中,根据另一列中的重复值将数据分组为一列

将 rgb numpy 图像转换为 rgb 列表和相应的索引值

我正在使用 python 线程,当查询 mysql 时,代码似乎在运行并保持在无限循环中,没有返回任何错误

为什么 Sympy 不能解决我的非线性系统? Python 解释器一直在执行,直到我终止进程

正则表达式来识别用 Python 写成单词的数字?

FastAPI - 调用 API 时设置 response_model_exclude

运行 pip install -r requirements.txt 时出错

Pandas 将列格式化为货币

基本 Flask 应用程序未运行(TypeError:模块中缺少必填字段type_ignores)

__new__ 方法给出错误 object.__new__() 只接受一个参数(要实例化的类型)

如何用pymongo连接远程mongodb

如何在 python 3.x 中禁用 ssl 判断?

Python 的 unittest 和 unittest2 模块有什么区别?

如何使用已打开并使用登录凭据登录的浏览器

对字节进行按位运算

Python,Docker - ascii编解码器无法编码字符