鸭子输入失败时诊断错误

时间:2014-02-22 21:36:34

标签: python duck-typing

我有一个“工厂”,它使用参数元组创建类的实例:

def factory(cls, arg):
  return cls(*arg)

class Foo(object):
  def __init__(self, a, b):
    pass

a = (1,2)
f = factory(Foo, a)

这很好用,所以我决定(由于各种原因超出了这个问题的范围)添加对不带任何参数作为回退的类的支持,以便可以在现有代码中更广泛地使用它。所以我需要检测2-arg vs 0-arg构造函数的可用性并适当地回退。鸭子打字似乎是“Pythonic”的答案并且工作得很漂亮:

def factory(cls, arg):
  try:
    return cls(*arg)
  except TypeError:
    print("Missing 2-arg, falling back to 0-arg ctor")
    return cls()

class Foo(object):
  def __init__(self,a,b):
    pass

class Bar(object):
  def __init__(self,a):
    pass

a = (1,2)
f = factory(Foo, a)
b = factory(Bar, a)

但是当其中一个__init__函数内部出现错误时,问题就出现了。当0-arg和2-arg __init__都不存在时,我试图提供帮助并发出警告:

def factory(cls, arg):
  try:
    return cls(*arg)
  except TypeError:
    print("%s Missing 2-arg, falling back to 0-arg ctor" % str(cls))
    return cls()

class Foo(object):
  def __init__(self,a,b):
    iter(a) # Oops, TypeError

a = (1,2)
try:
  f = factory(Foo, a)
except TypeError: # 0-arg must be missing too
  print("Neither 2-arg nor 0-arg ctor exist, that's all we support, sorry")

但是这里有一个致命的缺陷 - 我无法区分由于没有合适的TypeError而引发的__init__和因为问题较深而引发的TypeError __init__

我不是在寻找修复FooBar的方法,而是在“工厂”或其来电者层面更准确地理解失败的方法。

我如何以编程方式判断TypeError是否是无过载匹配或其他地方失败的结果?我现在最好的想法是以编程方式走堆栈跟踪并查看行号,这最多是可怕的。

1 个答案:

答案 0 :(得分:1)

测试__init__方法所需的参数数量可以让您分离两种失败模式。

class Foo(object):
    def __init__(self,a,b):
        pass

class Bar(object):
    def __init__(self,a):
        pass

class Baz(object):
    def __init__(self):
        pass

def ctr_arg_count(cls):
    fn = getattr(cls, '__init__')
    return len(inspect.getargspec(fn).args) - 1

def factory(cls, *args):
    if len(args) != ctr_arg_count(cls):
        print("Can't initialize %s - wrong number of arguments" % cls)
        return None
    return cls(*args)

print factory(Foo, 1, 2)
print factory(Bar, 1)
print factory(Baz)
print factory(Foo, 1)

>>> <__main__.Foo object at 0x100483d10>
    <__main__.Bar object at 0x100483d10>
    <__main__.Baz object at 0x100483d10>
    Can't initialize <class '__main__.Foo'> - wrong number of arguments
    None