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