我有一组不相关的类(有些是导入的(,它们都有一个dict[str, Any]
类型的公共属性(或属性(a
。
在a
中,键"b"
下应该有另一个dict,我想将其作为属性b
公开在这些类中的任何一个上,以将inst.a.get("b", {})[some_key]
简化为inst.b[some_key]
。
我制作了以下子类工厂,作为本地类的类装饰器和导入类的函数。
但到目前为止,我未能正确键入hint的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(有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"]
我不明白为什么
cls
作为一种类型无效,即使在阅读之后也是如此https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs类型别名。我知道我可能不应该返回Protocol(我怀疑这是
Type[<nothing>]
的来源(,但我不知道如何指定";返回具有扩展名"的原始类型;。
PS1.我也尝试过一个动态添加b的装饰器,但仍然无法键入…
PS2.…根据@DaniilFajnberg的回答,装饰器使用了一个mixin,但仍然失败。
参考文献:
functools.wraps(cls, update=())
https://stackoverflow.com/a/65470430/17676984
(类型(变量作为基类
这实际上是一个非常有趣的问题,我很好奇其他人能想出什么解决方案。
我读了一些关于这两个错误的文章:
变量"cls";作为类型无效/基类"无效";cls";
mypy
似乎有一个问题,它已经开放了很长时间。似乎还没有解决办法。
据我所知,问题在于,无论你如何注释它,函数参数cls
都将始终是一个类型变量,并且作为基类被认为是无效的。其理由显然是,无法确保该变量的值不会在某个地方被覆盖。
老实说,我对其中的复杂性还不够理解,但mypy
似乎对待通过class A: ...
定义的类A
,而不是Type[A]
的变量,这对我来说真的很奇怪,因为前者本质上应该只是语法糖:
A = type('A', (object,), {})
mypy
问题跟踪器中也进行了相关讨论。再次,希望有人能照亮这一点。
添加便利属性
无论如何,从你的例子中,我了解到你不是在处理外部类,而是你自己定义它们。如果是这样的话,混合将是最简单的解决方案:
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"])
输出:True
--strict
模式下没有mypy
问题。
与外国班混合
如果您正在处理外来类,您仍然可以使用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__=}')
输出:
True
MixedForeign.__name__='Foreign' MixedForeign.__doc__='Foreign class documentation'
在--strict
模式下也没有mypy
问题。
请注意,您仍然需要AProtocol
来明确该属性中的任何self
都遵循该协议,即具有类型为dict[str, Any]
的属性a
。
我希望我正确理解了您的要求,这至少为您的特定情况提供了一个解决方案,尽管我无法就类型变量问题向您提供启示。