如何避免对Python函数的参数进行类型检查

5

我正在创建一个名为Foo的类的实例,我想能够从各种类型中以一般方式实例化这些实例。您不能将字典或列表传递给Foo。请注意,Foo来自第三方代码库-我无法更改Foo的代码。

我知道在Python中检查函数参数的类型被认为是不好的形式。是否有一种更Pythonic的方法编写下面的函数(即不进行类型检查)?

def to_foo(arg):
  if isinstance(arg, dict):
    return dict([(key,to_foo(val)) for key,val in arg.items()])
  elif isinstance(arg, list):
    return [to_foo(i) for i in arg]
  else:
    return Foo(arg)

编辑:可以使用try/except块。例如,您可以这样做:

def to_foo(arg):
  try:
    return Foo(arg)
  except ItWasADictError:
    return dict([(key,to_foo(val)) for key,val in arg.items()])
  except ItWasAListError:
    return [to_foo(i) for i in arg]

我对此并不完全满意,原因有二:首先,类型检查似乎更直接地解决了所需的功能,而这里的try/except块似乎是在间接地达到相同的目的。其次,如果错误不能像这样干净地映射怎么办?(例如,如果传递列表或字典会引发TypeError)。
编辑:我不太喜欢在这里使用try/except方法的第三个原因是我需要去找出Foo在这些情况下会抛出什么异常,而不能事先编码。

1
可能是重复的问题:检查函数参数类型是否符合Pythonic风格? - aruisdante
1
一般来说,我认为这三个函数是完全独立的。它们返回不同类型,没有共同的接口,所以我看不到你不知道将要从哪种类型构造“Foo”的地方,因为你需要知道如何处理“Foo”、“list(Foo)”或“dict((str, Foo))”的返回。 - aruisdante
1
请注意,在Python中进行任何类型检查时,不会沿着继承链查找,只会查找直接实例类型。这意味着,对于像“列表”(例如集合或任何可迭代对象)或像“字典”一样的东西(例如OrderedDict或defaultdict),您的类型检查将始终失败。“singledispatch”略微好一些,因为它将沿着继承链查找共同的祖先,但仍将无法通过acts-like测试。 - aruisdante
1
我同意,可以预先知道传递的对象是列表还是字典,并进行适当处理。我希望有一个函数能够优雅地处理我感兴趣的大多数情况,例如单个实例、列表、字典、列表中的列表等。似乎try/except块可以做到这一点,但不能保证异常对于您可能想要支持的每种类型都是唯一的。 - user1336934
2个回答

4
如果您使用的是Python 3.4,则可以使用functools.singledispatch,或者不同Python版本的后端移植
from functools import singledispatch

@singledispatch
def to_foo(arg):
    return Foo(arg)

@to_foo.register(list)
def to_foo_list(arg):
    return [Foo(i) for i in arg]

@to_foo.register(dict)
def to_foo_dict(arg):
    return {key: Foo(val) for key, val in arg.items()}

这是Python中相对较新的构造,但在其他语言中是常见的模式。我不确定是否称之为Pythonic,但它确实比到处编写isinstance要好得多。尽管在实践中,singledispatch可能只是在内部为您执行isinstance检查。

+1 - 我认为我更倾向于使用isinstance,因为它比定义三个不同的函数更简洁和直接。 - user1336934
@user1336934 对于这个特定的用例,我同意你的看法。如果各种函数体中有更多的代码和逻辑,我认为我更喜欢使用singledispatch。 - Josh Smeaton
从源代码来看,@JoshSmeaton说:“实际上,singledispatch 可能只是在内部为您执行 isinstance 检查。” 看起来 singledispatch 只是维护了一个函数字典,其中键是传递给 register 调用的类型。 因此,从技术上讲,它是哈希表查找而不是 isinstance 检查。 - ebarr
@ebarr谢谢你提到这个。我很好奇,去检查了一下源代码,看看他们使用了什么方法,但忘记更新答案了。 - Josh Smeaton

3

处理您的问题的Pythonic方法是先假设(首先)argFoo,并接受任何错误:

try:
    x = Foo(arg)
except NameError:
    #do other things

这个概念的短语是"鸭子类型", 它是Python中流行的一种模式。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接