什么是类型重载的 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_action
和do_dict_action
中使用的所有函数是否存在,以确定调用哪个函数?
@Woofas:不,isinstance
并没有被一些理想主义者“皱眉”。你可以按照你说的去做——检查你需要的特性是否出现在你收到的参数中。又名“鸭子打字”。以上是关于什么是类型重载的 python 等价物?的主要内容,如果未能解决你的问题,请参考以下文章
什么是 PHP 的 var_dump() 的 Python 等价物? [复制]