[英]Type hinting a class decorator that returns a subclass
I have a set of unrelated classes (some imported) which all have a common attribute (or property) a
of type dict[str, Any]
.我有一组不相关的类(一些是导入的),它们都有a
dict[str, Any]
类型的公共属性(或属性)。
Within a
there should be another dict under the key "b"
, which I would like to expose on any of these classes as an attribute b
to simplify inst.a.get("b", {})[some_key]
to inst.b[some_key]
.在a
中,键"b"
下应该有另一个字典,我想在这些类中的任何一个上公开它作为属性b
以简化inst.a.get("b", {})[some_key]
到inst.b[some_key]
。
I have made the following subclass factory to work as a class decorator for local classes and a function for imported classes.我已将以下子类工厂用作本地类的 class 装饰器和导入类的 function 装饰器。
But so far I'm failing to type hint its cls
argument and return value correctly.但到目前为止,我未能正确输入提示其cls
参数和返回值。
from functools import wraps
def access_b(cls):
@wraps(cls, updated=())
class Wrapper(cls):
@property
def b(self) -> dict[str, bool]:
return self.a.get("b", {})
return Wrapper
MRE of my latest typing attemp (with mypy 0.971
errors):我最新的打字尝试的 MRE(带有mypy 0.971
错误):
from functools import wraps
from typing import Any, Protocol, TypeVar
class AProtocol(Protocol):
a: dict[str, Any]
class BProtocol(AProtocol, Protocol):
b: dict[str, bool]
T_a = TypeVar("T_a", bound=AProtocol)
T_b = TypeVar("T_b", bound=BProtocol)
def access_b(cls: type[T_a]) -> type[T_b]:
@wraps(cls, updated=())
class Wrapper(cls): # Variable "cls" is not valid as a type & Invalid base class "cls"
@property
def b(self) -> dict[str, bool]:
return self.a.get("b", {})
return Wrapper
@access_b
class Demo1:
"""Local class."""
def __init__(self, a: dict[str, Any]):
self.a = a.copy()
demo1 = Demo1({"b": {"allow_X": True}})
demo1.b["allow_X"] # "Demo1" has no attribute "b"
class Demo2:
"""Consider me an imported class."""
def __init__(self, a: dict[str, Any]):
self.a = a.copy()
demo2 = access_b(Demo2)({"b": {"allow_X": True}}) # Cannot instantiate type "Type[<nothing>]"
demo2.b["allow_X"]
I do not understand why cls
is not valid as a type, even after reading https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases .我不明白为什么cls
作为一种类型无效,即使在阅读https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases之后也是如此。
I understand I should probably not return a Protocol (I suspect that is the source of Type[<nothing>]
), but I don't see how I could specify "returns the original type with an extension".我知道我可能不应该返回协议(我怀疑这是Type[<nothing>]
的来源),但我不知道如何指定“返回带有扩展名的原始类型”。
PS1. PS1。 I have also tried with a decorator which adds b dynamically, still failed to type it...我也尝试过使用动态添加 b 的装饰器,但仍然无法输入...
PS2. PS2。 ...and with a decorator which uses a mixin as per @DaniilFajnberg's answer, still failing. ...并且根据@DaniilFajnberg的回答使用了一个使用mixin的装饰器,仍然失败。
References:参考:
functools.wraps(cls, update=())
from https://stackoverflow.com/a/65470430/17676984 functools.wraps(cls, update=())
来自https://stackoverflow.com/a/65470430/17676984This is actually a really interesting question and I am curious about what solutions other people come up with.这实际上是一个非常有趣的问题,我很好奇其他人提出了什么解决方案。
I read up a little on these two errors:我对这两个错误进行了一些阅读:
Variable "cls" is not valid as a type / Invalid base class "cls"变量“cls”作为类型无效/无效基 class“cls”
There seems to be an issue here with mypy
that has been open for a long time now.这里似乎有一个问题, mypy
已经打开了很长时间。 There doesn't seem to be a workaround yet.似乎还没有解决方法。
The problem, as I understand it, is that no matter how you annotate it, the function argument cls
will always be a type variable and that is considered invalid as a base class.据我了解,问题在于无论您如何注释它,function 参数cls
将始终是一个类型变量,并且作为基础 class 被认为是无效的。 The reasoning is apparently that there is no way to make sure that the value of that variable isn't overwritten somewhere.原因显然是没有办法确保该变量的值不会在某处被覆盖。
I honestly don't understand the intricacies well enough, but it is really strange to me that mypy
seems to treat a class A
defined via class A: ...
different than a variable of Type[A]
since the former should essentially just be syntactic sugar for this:老实说,我不太了解其中的复杂性,但我真的很奇怪mypy
似乎处理了通过 class A
定义的class A: ...
不同于Type[A]
的变量,因为前者本质上应该只是语法糖:
A = type('A', (object,), {})
There was also a related discussion in the mypy
issue tracker. mypy
问题跟踪器中也有相关讨论。 Again, hoping someone can shine some light onto this.再次,希望有人可以对此有所启发。
In any case, from your example I gather that you are not dealing with foreign classes, but that you define them yourself.无论如何,从您的示例中,我了解到您不是在处理外国类,而是您自己定义它们。 If that is the case, a Mix-in would be the simplest solution:如果是这种情况,Mix-in 将是最简单的解决方案:
from typing import Any, Protocol
class AProtocol(Protocol):
a: dict[str, Any]
class MixinAccessB:
@property
def b(self: AProtocol) -> dict[str, bool]:
return self.a.get("b", {})
class SomeBase:
...
class OwnClass(MixinAccessB, SomeBase):
def __init__(self, a: dict[str, Any]):
self.a = a.copy()
demo1 = OwnClass({"b": {"allow_X": True}})
print(demo1.b["allow_X"])
Output: True
Output: True
No mypy
issues in --strict
mode. --strict
模式下没有mypy
问题。
If you are dealing with foreign classes, you could still use the Mix-in and then use functools.update_wrapper
like this:如果您正在处理外部类,您仍然可以使用 Mix-in,然后像这样使用functools.update_wrapper
:
from functools import update_wrapper
from typing import Any, Protocol
class AProtocol(Protocol):
a: dict[str, Any]
class MixinAccessB:
"""My mixin"""
@property
def b(self: AProtocol) -> dict[str, bool]:
return self.a.get("b", {})
class Foreign:
"""Foreign class documentation"""
def __init__(self, a: dict[str, Any]):
self.a = a.copy()
class MixedForeign(MixinAccessB, Foreign):
"""foo"""
pass
update_wrapper(MixedForeign, Foreign, updated=())
demo2 = MixedForeign({"b": {"allow_X": True}})
print(demo2.b["allow_X"])
print(f'{MixedForeign.__name__=} {MixedForeign.__doc__=}')
Output: Output:
True
MixedForeign.__name__='Foreign' MixedForeign.__doc__='Foreign class documentation'
Also no mypy
issues in --strict
mode.在--strict
模式下也没有mypy
问题。
Note that you still need the AProtocol
to make it clear that whatever self
will be in that property follows that protocol, ie has an attribute a
with the type dict[str, Any]
.请注意,您仍然需要AProtocol
来明确该属性中的任何self
都遵循该协议,即具有类型为dict[str, Any]
的属性a
。
I hope I understood your requirements correctly and this at least provides a solution for your particular situation, even though I could not enlighten you on the type variable issue.我希望我正确理解了您的要求,这至少为您的特定情况提供了解决方案,即使我无法就类型变量问题向您提供启发。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.