类型检查动态添加的属性 [英] Typechecking dynamically added attributes

查看:31
本文介绍了类型检查动态添加的属性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在编写特定于项目的 pytest 插件时,我经常发现 Config 对象对于附加我自己的属性很有用.示例:

When writing project-specific pytest plugins, I often find the Config object useful to attach my own properties. Example:

from _pytest.config import Config


def pytest_configure(config: Config) -> None:
    config.fizz = "buzz"

def pytest_unconfigure(config: Config) -> None:
    print(config.fizz)

显然,_pytest.config.Config 类中没有 fizz 属性,因此在上面的代码片段上运行 mypy 会产生

Obviously, there's no fizz attribute in _pytest.config.Config class, so running mypy over the above snippet yields

conftest.py:5: error: "Config" has no attribute "fizz"
conftest.py:8: error: "Config" has no attribute "fizz"

(请注意,pytest 还没有带有类型提示的版本,因此如果您想在本地实际重现错误,请按照 此评论).

(Note that pytest doesn't have a release with type hints yet, so if you want to actually reproduce the error locally, install a fork following the steps in this comment).

有时为类型检查重新定义类可以提供快速帮助:

Sometimes redefining the class for typechecking can offer a quick help:

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from _pytest.config import Config as _Config

    class Config(_Config):
        fizz: str

else:
    from _pytest.config import Config



def pytest_configure(config: Config) -> None:
    config.fizz = "buzz"

def pytest_unconfigure(config: Config) -> None:
    print(config.fizz)

然而,除了使代码混乱之外,子类化的解决方法非常有限:添加例如

However, aside from cluttering the code, the subclassing workaround is very limited: adding e.g.

from pytest import Session


def pytest_sessionstart(session: Session) -> None:
    session.config.fizz = "buzz"

将迫使我也覆盖 Session 以进行类型检查.

would force me to also override Session for typechecking.

解决此问题的最佳方法是什么?Config 就是一个例子,但我通常在每个项目中都有几个(针对测试收集/调用/报告等的项目特定调整).我可以想象编写我自己版本的 pytest 存根,但是我需要为每个项目重复这个,这非常乏味.

What is the best way to resolve this? Config is one example, but I usually have several more in each project (project-specific adjustments for test collection/invocation/reporting etc). I could imagine writing my own version of pytest stubs, but then I would need to repeat this for every project, which is very tedious.

推荐答案

这样做的一种方法是设法让您的 Config 对象定义 __getattr__>__setattr__ 方法.如果这些方法是在类中定义的,mypy 将使用这些方法来检查您正在访问或设置某些未定义属性的位置.

One way of doing this would be to contrive to have your Config object define __getattr__ and __setattr__ methods. If those methods are defined in a class, mypy will use those to type check places where you're accessing or setting some undefined attribute.

例如:

from typing import Any

class Config:
    def __init__(self) -> None:
        self.always_available = 1

    def __getattr__(self, name: str) -> Any: pass

    def __setattr__(self, name: str, value: Any) -> None: pass

c = Config()

# Revealed types are 'int' and 'Any' respectively
reveal_type(c.always_available)
reveal_type(c.missing_attr)

# The first assignment type checks, but the second doesn't: since
# 'already_available' is a predefined attr, mypy won't try using
# `__setattr__`.
c.dummy = "foo"
c.always_available = "foo"

如果您确定您的临时属性始终是 strs 或其他内容,您可以输入 __getattr____setattr__ 以返回或接受 str 而不是 Any 分别以获得更严格的类型.

If you know for certain your ad-hoc properties will always be strs or something, you could type __getattr__ and __setattr__ to return or accept str instead of Any respectively to get tighter types.

不幸的是,您仍然需要使用子类型技巧或制作自己的存根——这给您带来的唯一优势是您至少不必列出要设置的每个自定义属性并使创造真正可重用的东西成为可能.这可能会让您觉得这个选项更可口,但不确定.

Unfortunately, you would still need to do the subtyping trick or mess around with making your own stubs -- the only advantage this gives you is that you at least won't have to list out every single custom property you want to set and makes it possible to create something genuinely reusable. This could maybe make the option more palatable to you, not sure.

您可以探索的其他选项包括:

Other options you could explore include:

  • 只需在使用临时属性的每一行添加一个 # type: ignore 注释即可.这将是一种抑制错误消息的精确方式(如果是侵入性的).
  • 输入您的 pytest_configurepytest_unconfigure,以便它们接受 Any 类型的对象.这将是一种抑制错误消息的侵入性较小的方式.如果您想最小化使用 Any 的爆炸半径,您可以将任何想要使用这些自定义属性的逻辑限制在它们自己的专用函数中,并在其他任何地方继续使用 Config.
  • 尝试改用强制转换.例如,在 pytest_configure 中,您可以执行 config = cast(MutableConfig, config) 其中 MutableConfig 是您编写的子类 _pytest 的类.Config 并定义 __getattr____setattr__.这可能是上述两种方法之间的中间地带.
  • 如果向 Config 和类似的类添加临时属性是一种常见的事情,也许可以尝试说服 pytest 维护者包含仅键入的 __getattr____setattr__ 在他们的类型提示中定义——或者让用户添加这些动态属性的其他一些更专用的方式.
  • Just adding a # type: ignore comment to every line where you use an ad-hoc property. This would be a somewhat precise, if intrusive, way of suppressing the error messages.
  • Type your pytest_configure and pytest_unconfigure so they accept objects of type Any. This would be a somewhat less intrusive way of suppressing the error messages. If you want to minimize the blast radius of using Any, you could maybe confine any logic that wants to use these custom properties to their own dedicated functions and continue using Config everywhere else.
  • Try using casting instead. For example, inside pytest_configure you could do config = cast(MutableConfig, config) where MutableConfig is a class you wrote that subclasses _pytest.Config and defines both __getattr__ and __setattr__. This is maybe a middle ground between the above two approaches.
  • If adding ad-hoc attributes to Config and similar classes is a common kind of thing to do, maybe try convincing the pytest maintainers to include typing-only __getattr__ and __setattr__ definitions in their type hints -- or some other more dedicated way of letting users add these dynamic properties.

这篇关于类型检查动态添加的属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆