【问题标题】:Asserting a variable has certain properties if it passes certain tests如果变量通过某些测试,则断言变量具有某些属性
【发布时间】:2021-11-11 06:13:28
【问题描述】:

使用 python 类型系统,我有时知道变量或表达式的类型,因为我安装了运行时检查来验证它。有没有办法用注释来表示?

例如,假设我有一个函数combop,如果它的参数属于Combination 类,它会返回True。进一步假设Combination 类有一个名为tds 的字段。

if combop(x):
   return f(x.tds)
else:
   return f([])

在这个例子中,我想向检查器保证,在 if 的 then 部分中,x 的类型为 Combination,而x.tds 是有意义的。

这种情况的一个例子是here python-rte 的第 172 行。 IDE 抱怨d.tdsn.s。我假设有一种方法可以通过类型注释来解释我的意图,并且 IDE 会在可能的情况下接受或验证我的声明。

在某些函数式语言(例如 Scala)中完成此操作的方式是使用模式匹配。在模式匹配的子句中,类型推断器能够限制某些变量的类型。在其他一些语言(例如 Common Lisp)中,有一个 declare 语法允许程序员向类型推断器提供提示,这可能会或可能不会产生运行时开销,具体取决于编译器设置。

【问题讨论】:

  • @AlexWaygood,我不确定这是否回答了我的问题。在我看来,这是解决问题的一种非常奇怪的方式。我在想有一种方法可以做一些注释来保证某种类型的 x 对 .tds 有意义。
  • Python 的类型检查器不如静态类型语言中的类型检查器复杂,因此在 Scala 中相当容易标记的内容在 python 中通常会更复杂。我会看看我是否可以在答案中写一些想法。
  • 还有一个事实是,Python 的打字系统还是很新的,所以语法还在摸索中。可能随着时间的推移,该语言采用了更多在 Scala 中发现的想法。 github.com/python/typing/issues/813

标签: python type-hinting python-typing duck-typing


【解决方案1】:

Python 3.10 将引入新的TypeGuard 功能,可能为您提供所需的功能。 (该功能也可以通过 Python typing_extensions 模块获得。)TypeGuard 允许您向类型检查器断言对象属于某种类型。所以,你可以写一个这样的函数:

from typing import TypeGuard, Any

def combop(obj: Any) -> TypeGuard[Combination]:
    # Some code that returns `True` if the object is of type `Combination`, else `False`.

如果您对对象的 属性(“结构类型”而不是对象的“名义类型”)更感兴趣,那么您可以使用 TypeGuard 来断言你的对象符合某个protocol

from typing import Protocol, TypeGuard, Any

class CombinationsProto(Protocol):
    tds: list[Any]


def combops(obj: Any) -> TypeGuard[CombinationsProto]:
    # Some code here that returns `True` if the object has a `tds` attribute, else `False`.

由于这是 python 类型世界中的一项新功能,Python 的主要类型检查器仍在努力支持此功能,特别是对于一些复杂的 corner cases

【讨论】:

  • 只是一个澄清问题。 TypeGuard 似乎工作得很好。你知道filter是否兼容TypeGuard吗?例如,如果x 的类型为List[X],并且combop 被声明为返回TypeGuard[Combination],那么mypy 似乎认为list(filter(combop,x)) 的类型为List[X] 而不是List[Combination]。您认为这是可以预期的吗?
  • @JimNewton 听起来很不幸。我不确定这是否是(a)因为它是一个新功能,而 MyPy 仍在处理一些关于他们如何实现它的极端案例,或者(b)它总是如何工作。除非已经被问到,否则我可能会向 MyPy 提交错误报告以要求澄清?
  • 貌似还没有编辑器支持youtrack.jetbrains.com/issue/PY-49040
  • @JimNewton 这似乎是 Pycharm 的错误报告,不是吗? MyPy 不是 JetBrains 产品,其实现方式与 pycharm 的类型检查器不同。 (它通常也少了很多错误!)
  • @JimNewton 这看起来更相关 imo github.com/python/mypy/issues/9927
【解决方案2】:
import typing


class Foo:
    def __init__(self) -> None:
        self.bar = "zebra"  # will be of 'str' type


def give_me_something():  # no annotation here, can't tell what gets returned
    if dict():  # an empty dict is always False, this is just an example, a real condition could be used
        return []
    else:
        return Foo()


reveal_type(give_me_something())
reveal_type(typing.cast(Foo, give_me_something()))
$ mypy so69205255.py
so69205255.py:16: note: Revealed type is "Any"
so69205255.py:17: note: Revealed type is "so69205255.Foo"

我使用 MyPy 进行可重复检查,但我的 IDE PyCharm 也能理解:当我悬停分配表达式的变量名时,它说第一个是 Union[list, Foo] 类型,而第二个是 @987654326 类型@。

更多信息:见the docs

【讨论】:

  • 抱歉,我看不到您的帖子如何解决我的问题。我已经读过几遍了。这对我来说似乎离题了。
  • typing.cast 是一种约定,用于指示类型检查器是变量的类型。
  • 你是指变量类型还是表达式类型?即,仅强制转换变量而不是中间表达式有效?当我查看文档 mypy.readthedocs.io/en/stable/casts.html 时,它并没有明确说明这仅适用于变量,但是,显示的唯一示例是变量定义。
  • @JimNewton 从语义上讲,cast 是一个空操作,它只返回给定的值。但它是类型检查器(MyPy 或 IDE 中的那个)的 标记,以 假定 值是指定的类型。所以cast(int, "foo") 被认为是一个int,因为cast 是这么说的。这就是我想在我的回答中展示的,我没有让你理解。您问“我想向检查器保证 [...] x 具有组合类型”,cast 就是答案。实际上,在示例中它仅用于分配,但它可以在任何地方使用,就像我在答案示例中所做的那样。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-29
  • 1970-01-01
相关资源
最近更新 更多