类型提示协议的附加属性?

Type hinting additional attribute of a protocol?

是否可以键入提示 class 的属性的附加属性?

我有一个 class 属性 Outer.inner,它可以是符合协议 InnerProto.

的 class 的任何实例
#### I do not control this code #######
from typing import Protocol

class InnerProto(Protocol):
    arg: int

class OuterProto(Protocol):
    inner: InnerProto

Innerclass符合协议InnerProto,但它还有一个额外的属性(据我了解,这不违反协议一致性)

class Outer:
    def __init__(self, inner: InnerProto):
        self.inner: InnerProto = inner

class Inner:
    def __init__(self, arg: int):
        self.arg: int = arg  # this is part of the protocol
        self.arg2: int = arg  # this is not

Pycharm (v2021.2) 检查将属性 arg2 突出显示为未解析的属性。

def test_type_hint_example():
    obj = Outer(Inner(1))
    assert obj.inner.arg == 1
    assert obj.inner.arg2 == 1 # inspection highlights arg2 as "unresolved attribute"

是否可以输入提示以便检查识别 arg2 是什么?

通常我会倾向于更改 Outer.inner 的类型提示,但在这种情况下那将是一个错误。 inner 可以是任何符合 InnerProto.

的 class

我的第二个想法是覆盖实例属性的类型提示。这种类型的技巧对非属性对象有效,但在这种情况下会导致不同的检查错误:“Non-self attribute could not be type hinted”

def test_option1():
    obj = Outer(Inner(1))
    obj.inner: Inner   # fixes inspection for arg2, but "Non-self attribute could not be type hinted" occurs
    assert obj.inner.arg == 1
    assert obj.inner.arg2 == 1

还有哪些其他选择?

我认为这里的问题是代码不是类型安全的。

您的 Outer.__init__ 函数是:

class Outer:
    def __init__(self, inner: InnerProto):
        self.inner = inner

    # <-- snip -->

您用 InnerProto 注释了 inner 参数。这意味着 Outer.inner 属性可以是 任何 类型,只要该类型符合 InnerProto 协议。

根据您的说法,这是正确的类型提示:

Normally I would be inclined to alter the type hint of Outer.inner but in this case that would be a mistake. inner can be ANY class that conforms to InnerProto.

如果是这种情况,则静态类型检查器无法验证 Outer.inner 是否具有 InnerProto 中未指定的任何属性或方法。这就是类型检查器在您的 test_type_hint_example 函数中引发错误的原因:您已经告诉类型检查器 Outer.inner 可以是任何类型,只要该类型符合 InnerProto,但是 InnerProto 没有说明 arg2 属性,而且并非所有 python 类型都具有 arg2 属性,因此您可能会得到 AttribiteError 当你访问 Outer.inner.arg2.

如果您确定,在这个特定函数的上下文中,Outer.inner将是Inner,因此可以保证具有 arg2 属性——尽管事实上在大多数情况下,Outer.inner“可以是任何符合 InnerProto 的 class” — 然后你可以像这样使用 typing.cast

from typing import cast

def test_type_hint():
    obj = Outer(Inner(1))
    inner = cast(Inner, obj.inner)
    assert inner.arg == 1
    assert inner.arg2 == 1

然而,实际上,如果您 确定 Outer.inner 可以保证具有 arg2 属性,则很可能表明您确实,实际上, Outer.inner 的类型提示错误(如果不查看更多代码库,很难告诉您正确的类型提示是什么)。以我上面刚刚演示的方式使用 typing.cast 通常只能作为最后的手段使用——它更像是一种逃避类型检查的方法,从长远来看这对你没有帮助 运行如果您的代码不是类型安全的。