【问题标题】:Type hinting additional attribute of a protocol?类型提示协议的附加属性?
【发布时间】:2021-10-20 12:01:58
【问题描述】:

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

我有一个类属性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"

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

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

我的第二个想法是覆盖实例属性的类型提示。这种类型的技巧适用于非属性对象,但在这种情况下,它会导致不同的检查错误:“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

还有哪些其他选择?

【问题讨论】:

    标签: python python-3.x pycharm type-hinting python-typing


    【解决方案1】:

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

    您的Outer.__init__ 函数是:

    class Outer:
        def __init__(self, inner: InnerProto):
            self.inner = inner
    
        # <-- snip -->
    

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

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

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

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

    如果您确定,在这个特定函数的上下文中,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 通常只能作为最后的手段使用——它更像是一种逃避类型检查器的方法,如果你的代码不是,从长远来看它对你没有帮助类型安全。

    【讨论】:

    • yoru ratiobnale 没问题,但是正如您所说,测试没有多大意义:它正在测试测试代码本身中的显式转换。在这种情况下,如果 Inner.arg2 预计会在 Outer 代码中使用,它应该像这样注释 - Outer 应该需要一个特定的 Inner 对象,或者应该声明另一个协议,注释arg2 也是。
    • @jsbueno 我完全同意。我怀疑 OP 声称将类型提示更改为更具体的内容“将是一个错误”,因此我说我相信代码从根本上不是类型安全的。在这种情况下使用cast 会避开类型检查器而不是注意它的警告。可能我说得不够清楚。
    • @jsbueno "test" 函数是从原始问题中复制而来的——此时 MyPy 标记了原始代码中的错误。我同意在显式转换后立即使用asserts 没有多大意义;更多的是表明如果您以这种方式使用typing.cast,MyPy 将不再在该特定点引发错误。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-10-26
    • 2018-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-20
    相关资源
    最近更新 更多