近期咱们开源了一个跨结构算法评测库 MMEval( github.com/open-mmlab/… )。在 MMEval 中,咱们运用了一种叫做多分配的技术,来支撑不同结构完成的主动分发。

在日常代码的编写中运用多分配技术,能够防止大量重复的类型判别句子,让代码愈加简略易懂,不仅如此,还能够让代码具有愈加灵敏的扩展才干。 本文将给咱们主要介绍在 Python 中依据参数类型提示的多分配机制,快一起来学习下吧~

导言

假定咱们有以下一段代码,作用是依据输入数据的类型,回来对应的字符串。这是一段典型的多分支履行结构,而且是依据类型判别来承认履行途径,这是在咱们往常的 Python 代码编写中经常会遇到的景象。

让咱们来看一下在日常 Python 代码编写中经常会遇到的景象:下面这段代码,其作用是依据输入数据的类型,回来对应的字符串。这是一段典型的多分支履行结构,而且是依据类型判别来承认履行途径。

def check_type(x):
    if isinstance(x, int):
        return "this is int"
    elif isinstance(x, float):
        return "this is float"
    elif isinstance(x, str):
        return "this is str"
    else:
        return f"unknown type: {type(x)}"

这段代码在大部分时分是可承受的,可是在一些情况下,或许会需求拓展新的判别分支,而且上述代码块对类型的判别有大量重复的句子。

针对上述情况,咱们能够运用别的一种叫做多分配的编程形式,将类型判别的部分隐藏起来,经过类型注释来声明分支履行条件。从而防止了大量重复的类型判别句子,而且具有愈加灵敏的扩展才干。

假定咱们现已完成了一个多分配的装修器 dispatch,运用 dispatch 装修函数之后,能够依据输入的数据类型主动调用相应的完成,咱们能够用以下代码来完成上述功用:

@dispatch
def check_type(x: int):
    return "this is int"
@dipatch
def check_type(x: float):
    return "this is float"
@dipatch
def check_type(x: str):
    return "this is float"

咱们利用 dispatch 装修器,将大量的 if – else 类型判别分支去除。在函数界说时,经过类型提示来声明当时完成所需求匹配的数据类型,在函数调用时,依据运行时的参数类型来分发详细的完成。

这样做不仅让代码更简洁易懂,还有利于新分支的扩展。(有关于 Python 类型提示的文章能够点击查看:zhuanlan.zhihu.com/p/519335398)

多分配介绍

在代码完成过程中,咱们通常把某个详细处理逻辑封装成函数或许办法,有的时分咱们需求有一些同名函数,处理不同类型的输入,由此能够引出咱们刚刚比方里面说到的多分配机制

依据维基百科介绍,多分配是某些编程言语的特性,它允许函数或许办法,在运行时依据它的实际参数类型,或在更一般的情况下的其他特性,来动态分发详细的函数完成。

多分配来自于单分配多态的推广,所谓单分配便是在函数调用时分,除了函数名之外,还有别的一个函数的参数会被作为分发详细完成的依据,一般是函数的第一个参数。在一些面向目标的编程言语中,比方 C++ 和 JAVA ,这个参数一般是目标实例,在运行时会依据实际的类型来调用相应的办法,这种行为也叫做多态。

对于多分配来说,除了函数名之外,还要求函数的参数个数和类型都彻底对应上,才干决议详细调用哪个函数完成。现在许多言语都直接或许间接地支撑了多分配机制,Julia 更是把多分配作为言语的核心特色,针对不同的输入数据,经过 JIT 生成对应数据类型的高效字节码。 C# 和 R 等言语也内建支撑了多分配,别的像 C / C++ / Python 等言语则需求经过第三方扩展来支撑完成多分配。

Python 中的多分配完成

Python 本身没有内建支撑多分配机制,有两个或许的原因。第一个是 Python 作为动态类型的编程言语,要在运行时精确地获取其数据类型是一件开支很大的事情。第二个原因是 Python 本身便是一种十分灵敏的编程言语,即使是需求自己完成多分配,也不是一件难事。

早在 2005 年,Python 作者 Guido 就给咱们展示了如何在五分钟内为 Python 完成一个多分配机制:www.artima.com/weblogs/vie…

在 Python3.4 的时分,规范库 functools 引入了一个 singledispatch 装修器,将单分配机制引入 Python 中,它能够将函数转为单分配的泛函数,依据函数的第一个参数类型来分发详细的完成。如下是 singledispatch 的详细运用比方:

from functools import singledispatch
@singledispatch
def fun(x: int):
    return "this is int"
@fun.register
def _(x: float):
    return "this is float"
@fun.register
def _(x: str):
    return "this is str"

functools.singledispatch 是 Python 官方支撑的单分配机制,在这里能够把它理解为简化版的 Python 多分配,它仅能经过第一个参数的类型来决议函数分发。

coady/multimethod 是一个社区开发维护的多分配完成,运用方式与 functools.singledispatch 类似,能够支撑经过一切位置参数决议的函数分发,相较于 functools.singledispatch 提供了更强壮的功用。除了 coady/multimethod 以外, 社区中还有别的一些多分配的完成,比方 mrocklin/multipledispatch ,wesselb/plum 和 morepath/reg,其间 morepath/reg 还支撑依据谓词的分配。

上述的这几个多分配完成或多或少都存在一些问题,一个很重要的问题是他们大多都是仅支撑内建数据类型的类型提示, coady/multimethod 和 wesselb/plum 在外。

coady/multimethod 能够支撑任何满意 issubclass 的类型描绘,包括 collections.abc 中的类型基类以及 typing 模块中的基类型。

而 wesselb/plum 则自己完成了一套简易的类型体系,能够将 Python 目标和类型提示转换成自己的类型描绘,而且在此基础上求解最匹配的函数签名。

以下是 wesselb/plum 运用示例,比较 functools.singledispatch 在运用功用和运用方式上都有十分大的提高,现已与咱们最开端假定的 dispatch 装修器没有任何差异乃至愈加强壮:

from numbers import Number
from plum import dispatch
@dispatch
def f(x: str):
    return "This is a str!"
@dispatch
def f(x: int):
    return "This is an int!"
@dispatch
def f(x: Number):
    return "This is a general number, but I don't know which type."
>>> f("1")
'This is a str!'
>>> f(1)
'This is an int!'
>>> f(1.0)
"This is a number, but I don't know which type."
>>> f(object())
NotFoundLookupError: For function "f", signature Signature(builtins.object) could not be resolved.

Python 多分配存在的问题

Python 中依据参数类型提示的多分配,相较于多分支类型判别的结构,在代码可读性以及扩展性上都更具有优势。现在 Python 的多分配扩展完成也相当完善了,对于一些脚本的编写或许个人项目来说现已足够运用。可是要想将这种多分配机制应用到一些大的项目或许实际生产中,仍然有一些问题需求处理。

依据参数类型的多分配机制,需求处理的一个核心问题是类型判别与子类查看。而要做好类型判别与子类查看,首要便是需求获取到变量的数据类型,咱们在前文说到,Python 作为动态类型的编程言语,要在运行时精确地获取变量数据类型是一件开支不小的事情。

如果咱们在项目中把多分配机制作为基础组件,或许会引发性能问题。以 wesselb/plum 为例,当参数是一个百万级别列表时,承认该参数的类型或许会要花费数秒钟的时刻,这作为一个基础组件的调度开支来说是不能承受的。

针对此问题,MMEval 中的多分配也做了一些相应的优化,能够查看咱们的文档了解更多信息:mmeval.readthedocs.io/zh_CN/lates…

除此之外,缺少一个十分完备强壮的类型体系也是限制多分配机制应用的一大问题。自从 Python 3.5 引入 typing 模块后,Python 的类型提示变得越来越灵敏强壮,现在 coady/multimethod 与 wesselb/plum 对 typing 类型提示的支撑都不太完善,例如 SetTextIOAnyStr 等都没有支撑。

能够看出,高效精确的类型判别与子类查看是阻止多分配机制能够广泛运用的核心问题,现在在 Python 社区中,也有一些关于动态类型查看的东西,比方 beartype,能够做到十分快速的类型查看和子类判别,将此类东西应用到多分配中或许是一个不错的尝试。

参阅链接

  • github.com/open-mmlab/…
  • wikipedia – 多分配
  • Five-minute Multimethods in Python
  • 为什么要多重派发?(Why multiple dispatch?)
  • docs.python.org/3/library/f…
  • GitHub – coady/multimethod: Multiple argument dispatching.
  • github.com/mrocklin/mu…
  • github.com/morepath/re…
  • github.com/wesselb/plu…
  • github.com/wesselb/plu…
  • github.com/beartype/be…