类型检查动态添加的属性 [英] Typechecking dynamically added attributes
问题描述
在编写特定于项目的 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_configure
和pytest_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
andpytest_unconfigure
so they accept objects of typeAny
. This would be a somewhat less intrusive way of suppressing the error messages. If you want to minimize the blast radius of usingAny
, you could maybe confine any logic that wants to use these custom properties to their own dedicated functions and continue usingConfig
everywhere else. - Try using casting instead. For example, inside
pytest_configure
you could doconfig = cast(MutableConfig, config)
whereMutableConfig
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屋!