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

问题描述 投票:0回答:1

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

我有一个类属性

Outer.inner
,它可以是符合协议
InnerProto
的类的任何实例。

#### I do not control this code #######
from typing import Protocol

class InnerProto(Protocol):
    arg: int

class OuterProto(Protocol):
    inner: InnerProto

Inner
类符合协议
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"

enter image description here

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

通常我会倾向于更改

Outer.inner
的类型提示,但在这种情况下,这将是一个错误。
inner
可以是任何符合
InnerProto
的类。

我的第二个想法是覆盖实例属性的类型提示。 这种类型的技巧已经适用于非属性对象,但在这种情况下,它会导致不同的检查错误:“非自身属性无法进行类型提示”

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

enter image description here

还有哪些其他选择?

python python-3.x pycharm python-typing
1个回答
1
投票

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

您的

Outer.__init__
功能是:

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

    # <-- snip -->

您已用

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

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

通常我会倾向于更改

Outer.inner
的类型提示,但在这种情况下,这将是一个错误。
inner
可以是任何符合
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
的类”——那么你可以像这样使用
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
通常只能用作最后的手段 - 它更多的是逃避类型检查器的方法,从长远来看,如果你的代码不是这样,这对你没有帮助类型安全。

© www.soinside.com 2019 - 2024. All rights reserved.