0

我正在尝试在 Python 中设置一个类装饰器,它的作用类似于attr.frozen但在创建之前添加了一个额外的字段(以及其他一些东西)。虽然代码运行良好,但我无法让 mypy 意识到新类具有新字段。我试图通过一个自定义 mypy 插件(完全如's documentationattr所述)和一个Protocol定义新类具有给定字段的组合来做到这一点。总之,代码分解如下(都在一个文件中,尽管我在这里分解了它)。

应该注意我正在运行 Python 3.7,所以我typing_extensions在需要的地方使用,但我相信无论版本如何,这个问题都会持续存在。

首先定义应该通知 mypy 新类具有新字段(added在此处调用)的协议:

from typing_extensions import Protocol

class Proto(Protocol):
    def __init__(self, added: float, *args, **kwargs):
        ...
    @property
    def added(self) -> float:
        ...

现在根据文档field_transformer定义添加新字段的函数:attr

from typing import Type, List

import attr

def _field_transformer(cls: type, fields: List[attr.Attribute]) -> List[attr.Attribute]:
    return [
        # For some reason mypy has trouble with attr.Attribute's signature
        # Bonus points if someone can point out a fix that doesn't use type: ignore
        attr.Attribute (  # type: ignore
            "added",  # name
            attr.NOTHING,  # default
            None,  # validator
            True,  # repr
            None,  # cmp
            None,  # hash
            True,  # init
            False,  # inherited
            type=float,
            order=float,
        ),
        *fields,
    ]

现在,最后,设置一个类装饰器来做我们想要的:

from functools import wraps
from typing import Callable, TypeVar

_T = TypeVar("_T", bound=Proto)
_C = TypeVar("_C", bound=type)

def transform(_cls: _C = None, **kwargs):
    def transform_decorator(cls: _C) -> Callable[[], Type[_T]]:
        @wraps(cls)
        def wrapper() -> Type[_T]:
            if "field_transformer" not in kwargs:
                kwargs["field_transformer"] = _field_transformer
            return attr.frozen(cls, **kwargs)

        return wrapper()

    if _cls is None:
        return transform_decorator
    return transform_decorator(_cls)

现在对于(失败的)mypy 测试:

@transform
class Test:
    other_field: str

# E: Too many arguments for "Test"
# E: Argument 1 to "Test" has incompatible type "float"; expected "str"
t = Test(0.0, "hello, world")  
print(t.added)  # E: "Test" has no attribute "added"

理想情况下,我希望 mypy 消除所有这三个错误。坦率地说,我不确定这是否可能;可能是属性的动态添加是不可键入的,我们可能不得不强制我们库的用户在使用装饰器时编写自定义类型的存根。但是,由于我们总是向生成的类添加相同的属性,如果有解决方案会很棒,即使这意味着编写一个特别支持这个装饰器的自定义 mypy 插件(如果这甚至可能的话)。

4

0 回答 0