【问题标题】:Chained references in python type annotationspython类型注释中的链式引用
【发布时间】:2018-11-07 07:01:20
【问题描述】:

假设我有一个函数,它接受一个值和任意数量的函数,让我们调用该函数为 chain_call。

如果没有类型,一个简单的幼稚实现将是:

def chain_call(input_value, *args):
    for function in args:
        input_value = function(input_value)
    return input_value

正如您想象的那样,input_value 可以是任何东西,但它始终与*args: List[Callable] 中第一个Callable 的第一个也是唯一一个必需的参数相同。

从这里开始,Callable 的第一个也是唯一需要的参数的类型与前面的项返回类型相同。

到目前为止,我已经设法定义了一个非常通用的类型,但它太松散了。

def chain_call(input_value: Any, *args: List[Callable[Any], Any]) -> Any: ...

我真正想要的是类似的东西

T = TypeVar('T')

def chain_call(input_value: T, *args: List[Callable[T, ...], tr]) -> tr: ...

其中TCallable n+1trCallable n,最终的返回类型是Callable n_max 的tr。我不确定如何用类型系统表达这一点,希望得到任何指导。

【问题讨论】:

  • 我认为这在当前的typing 系统中是不可能的。在 github 站点上发布功能请求。 (mypy 和/或typing

标签: python python-3.x types type-hinting higher-kinded-types


【解决方案1】:

这个完全类型化的函数存在于dry-python/returns

我们称之为flow

from returns.pipeline import flow

assert flow('1', int, float, str) == '1.0'

问题是flow 是通过我们的库附带的自定义mypy 插件完全输入的。所以,它会捕获这个错误情况(和many others):

from returns.pipeline import flow

def convert(arg: str) -> float:
    ...

flow('1', int, convert)
# error: Argument 1 to "convert" has incompatible type "int"; expected "str"

文档:https://returns.readthedocs.io/en/latest/pages/pipeline.html

来源:https://github.com/dry-python/returns/blob/0f7d02d0c491a7c65c74e6c0645f12fccc53fe18/returns/_internal/pipeline/flow.py

插件:https://github.com/dry-python/returns/blob/0f7d02d0c491a7c65c74e6c0645f12fccc53fe18/returns/contrib/mypy/_features/flow.py

【讨论】:

  • 很高兴看到您发布了对我的旧问题的答案,我已经忘记了这一点。但我确实使用你的图书馆(和其他干蟒蛇)来做这些事情。保持伟大的工作!很想和你聊天:)
  • 太棒了!我们有一个聊天:t.me/drypython加入我们!
【解决方案2】:

不幸的是,目前这不是可以使用 PEP 484 类型提示键入的内容。

您可以做的最好的事情是使用重载来近似签名:基本上,我们将签名应该达到某个数字的硬编码,然后回退到推断“任何”:

from typing import TypeVar, overload, Any, Callable

T1 = TypeVar('T1')
T2 = TypeVar('T2')
T3 = TypeVar('T3')
T4 = TypeVar('T4')

@overload
def chain_call(input_value: T1, 
               *f_rest: Callable[[T1], T1]) -> T1: ...
@overload
def chain_call(input_value: T1, 
               f1: Callable[[T1], T2],
               f2: Callable[[T2], T3],
               f3: Callable[[T3], T4],
               f4: Callable[[T4], Any],
               *f_rest: Callable[[Any], Any]) -> Any: ...
@overload
def chain_call(input_value: T1, 
               f1: Callable[[T1], T2],
               f2: Callable[[T2], T3],
               f3: Callable[[T3], T4]) -> T4: ...
@overload
def chain_call(input_value: T1, 
               f1: Callable[[T1], T2],
               f2: Callable[[T2], T3]) -> T3: ...
@overload
def chain_call(input_value: T1, 
               f1: Callable[[T1], T2]) -> T2: ...
def chain_call(input_value, *f_rest):
    for function in f_rest:
        input_value = function(input_value)
    return input_value

在这里,我硬编码了最多 3 个输入函数应该发生的事情(并从所有可调用函数恰好具有相同输入和输出类型的特殊情况的重载开始)。

这种技术是目前 typeshed 对诸如 zip 函数之类的类型进行类型化的方式,该函数可以接受任意数量的可迭代对象。

注意:您可能需要使用 master 提供的最新版本的 mypy 才能使此代码逐字运行。

【讨论】:

  • 谢谢,我怀疑我不得不求助于这样的事情,但希望有人有更好的解决方案。
  • Michael0x2a:对于那些想要使用 vanilla python 和 mypy 的人来说,你的回答仍然很棒。但是改变了接受的答案,因为 sobolevn 实际上解决了根本问题,因此未来的搜索者可以看到使用起来不那么复杂的解决方案。
猜你喜欢
  • 2016-07-11
  • 1970-01-01
  • 2021-12-11
  • 2021-10-22
  • 2021-08-01
  • 2018-07-10
  • 1970-01-01
相关资源
最近更新 更多