ljm*_*jmc 5 python python-3.x python-decorators python-typing
我有一组不相关的类(一些是导入的),它们都有一个a类型为 的公共属性(或属性) dict[str, Any]。
其中akey 下应该有另一个字典"b",我想将其作为属性公开在任何这些类上b以简化inst.a.get("b", {})[some_key]为inst.b[some_key]。
我已将以下子类工厂用作本地类的类装饰器和导入类的函数。
但到目前为止,我未能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
Run Code Online (Sandbox Code Playgroud)
我最近一次打字尝试的 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"]
Run Code Online (Sandbox Code Playgroud)
我不明白为什么cls作为一种类型无效,即使在阅读https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases后也是如此。
我知道我可能不应该返回 Protocol (我怀疑这是 的来源Type[<nothing>]),但我不知道如何指定“返回带有扩展名的原始类型”。
PS1。我也尝试过使用动态添加 b 的装饰器,但仍然无法输入它......
PS2。...并且使用按照 @DaniilFajnberg 的答案使用 mixin 的装饰器,仍然失败。
参考:
functools.wraps(cls, update=())来自/sf/answers/4582930131/这实际上是一个非常有趣的问题,我很好奇其他人提出了什么解决方案。
我读了一些关于这两个错误的内容:
变量“cls”作为类型无效/基类“cls”无效
这里似乎有一个问题已经mypy存在很长时间了。似乎还没有解决方法。
据我了解,问题在于,无论您如何注释它,函数参数cls始终是类型变量,并且作为基类被认为是无效的。显然,原因是无法确保该变量的值不会在某处被覆盖。
老实说,我不太理解其中的复杂性,但对我来说真的很奇怪,mypy似乎对待A通过class A: ...不同于变量定义的类Type[A],因为前者本质上应该只是语法糖:
A = type('A', (object,), {})
Run Code Online (Sandbox Code Playgroud)
问题跟踪器中也有相关讨论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"])
Run Code Online (Sandbox Code Playgroud)
输出:True
模式下没有mypy问题--strict。
如果您正在处理外部类,您仍然可以使用 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__=}')
Run Code Online (Sandbox Code Playgroud)
输出:
True
MixedForeign.__name__='Foreign' MixedForeign.__doc__='Foreign class documentation'
Run Code Online (Sandbox Code Playgroud)
模式下也没有mypy问题--strict。
请注意,您仍然需要AProtocol明确self该属性中的任何内容都遵循该协议,即具有a类型为 的属性dict[str, Any]。
我希望我正确理解了您的要求,这至少为您的特定情况提供了解决方案,即使我无法就类型变量问题向您提供启发。