什么是类型重载的 python 等价物?

Posted

技术标签:

【中文标题】什么是类型重载的 python 等价物?【英文标题】:What would be the python equivalent of type-wise overloading? 【发布时间】:2017-06-01 02:07:52 【问题描述】:

据我所知,有两种类型的重载,一种基于参数数量,一种基于参数类型

虽然here 已经介绍了基于参数数量的重载,但我似乎无法找到有关按参数类型进行函数重载的指南。

因此,由于使用type() 进行类型检查似乎通常不被接受,我该如何以 Python 的方式执行此操作?

这似乎没有我想象的那么优雅......

def overloaded_func(arg):
    try:
        do_list_action(arg)
    except:
        try:
            do_dict_action(arg)
        except:
            pass

【问题讨论】:

我认为有一个可用的多方法实现,可能基于在装饰器中隐藏类型调度 【参考方案1】:

您可以使用functools.singledispatch。它将根据第一个参数的类型进行调度,如果类型与任何已注册的函数都不匹配,则使用默认实现:

from functools import singledispatch

@singledispatch
def overloaded_func(arg):
    print('Default impl', arg)

@overloaded_func.register(list)
def do_list_action(lst):
    print('List action', lst)

@overloaded_func.register(dict)
def do_list_action(dct):
    print('Dict action', dct)

overloaded_func(['foobar'])
overloaded_func('foo':'bar')
overloaded_func('foobar')

输出:

List action ['foobar']
Dict action 'foo': 'bar'
Default impl foobar

【讨论】:

这似乎比其他强类型语言更笨拙......但看到它是可用答案中最好的,我会接受这个【参考方案2】:

使用type()(或者,更好的是isinstance())进行类型检查一般在 Python 中并不令人不悦。不赞成的是使用类型作为行为的代理,而您只需检查行为即可。换句话说,当您需要一个对象具有某些功能时,而在某些其他语言中,您必须显式检查其类型是否支持该功能,在 Python 中,您只需假设该对象执行您需要它做的事情,并信任如果不是这种情况,将引发异常。但是,如果您在不同类型的功能之间进行选择,其中任何一种都可以为您完成工作,那就是另一回事了。

例如,假设您有一些代码可以使用列表或字典来实现整数索引数组。

class Array:
    def __init__(self, keys_and_values):
        self.store = ... # either a list or a dict

如果只有少数非常大的索引分配了值,则它可能使用字典,否则使用列表。如果你想访问某个索引处的元素,如果有的话,你就写self.store[index]

    def __getitem__(self, index):
        return self.store[index]

您不必先检查它是列表还是字典,因为您想要的行为 - 以整数为索引的能力 - 无论哪种方式都存在。

但是如果你想设置元素的索引,如果它是一个列表,你需要先将它扩展到适当的长度。现在,正确的鸭子类型可能会建议您这样做:

    def __setitem__(self, index, value):
        if index >= len(self.store):
            try:
                self.store.extend([None] * (index - len(self.store) + 1))
            except AttributeError:
                pass
        self.store[index] = value

但我认为大多数 Python 程序员会说 isinstance() 在这种情况下更好。 (不,真的。没关系。)

    def __setitem__(self, index, value):
        if isinstance(self.store, list) and index >= len(self.store):
            self.store.extend([None] * (index - len(self.store) + 1))
        self.store[index] = value

当您只有几种类型要测试时,我通常会推荐这条路线。

如果您有更多类型要测试,则使用调度程序模式更为实用,这是一种函数式方法。您构建类型到处理该类型的函数的映射,并根据您获得的对象的类型选择要调用的函数。在本例中,结果如下:

    def __setitem__dict(self, index, value):
        self.store[index] = value
    def __setitem__list(self, index, value):
        if index >= len(self.store):
            self.store.extend([None] * (index - len(self.store) + 1))
        self.store[index] = value
    __setitem__dispatch = list: __setitem__list, dict: __setitem__dict
    def __setitem__(self, index, value):
        self.__setitem__dispatch[type(self.store)](index, value)

在这个简单的示例中这样做非常愚蠢,但在更复杂的场景中它可以派上用场。一般的模式是

dispatch = list: handle_list, dict: handle_dict, ...
def function(arg):
    return dispatch[type(arg)](arg)

它甚至允许您稍后为新类型动态添加处理程序。这基本上就是functools.singledispatch 所做的(正如another answer 提到的那样)。这种方式看起来很复杂,因为它将 dispatch 字典隐藏为原始函数本身的属性。

一般来说,不可能说是否使用鸭子类型、类型检查、分派或其他方式,因为这有点主观,并且取决于您的具体情况:您需要处理不同类型的代码有多大不同?你要处理多少种类型?您是否需要能够轻松处理新类型?等等。您没有在问题中提供足够的信息,无法让其他人告诉您哪种方式看起来最好,但它们都有其用途。

【讨论】:

【参考方案3】:

使用isinstance:

def overloaded_func(arg):
    if isinstance(arg, list):
        do_list_action(arg)
    elif isinstance(arg, dict):
        do_dict_action(arg)
    else:
        do_default_action(arg)

或者,您可以考虑检查其他方式,例如是否存在__getitem____iter__ 等。这取决于您未与我们分享的超载原因的详细信息。

【讨论】:

不是isinstance()type() 一样不受欢迎吗?所以我得到的是我应该检查我在do_list_actiondo_dict_action 中使用的所有函数是否存在,以确定调用哪个函数? @Woofas:不,isinstance 并没有被一些理想主义者“皱眉”。你可以按照你说的去做——检查你需要的特性是否出现在你收到的参数中。又名“鸭子打字”。

以上是关于什么是类型重载的 python 等价物?的主要内容,如果未能解决你的问题,请参考以下文章

Python 等价于 R 的因子数据类型 [重复]

什么是“pythonic”的 Ruby 等价物? [关闭]

什么是 PHP 的 var_dump() 的 Python 等价物? [复制]

PostgreSQL 中 Long 数据类型的等价物是啥?

什么是matlab的imadjust在python中的等价物?

什么是 Python 的 pass 语句的 Bash 等价物