【问题标题】:catch wrong-arguments exception, in the general case在一般情况下捕获错误参数异常
【发布时间】:2011-11-22 04:55:10
【问题描述】:

我想捕获一个异常,但前提是它来自下一级逻辑。

目的是处理由错误数量的参数调用函数引起的错误,而不掩盖函数实现产生的错误。

如何实现下面的wrong_arguments函数?

例子:

try:
    return myfunc(*args)
except TypeError, error:
    #possibly wrong number of arguments
    #we know how to proceed if the error occurred when calling myfunc(), 
    #but we shouldn't interfere with errors in the implementation of myfunc
    if wrong_arguments(error, myfunc):
        return fixit()
    else:
        raise

附录:

有几种解决方案在简单的情况下工作得很好,但目前的答案都不能在装饰函数的实际情况下工作。

考虑以上myfunc 的可能值:

def decorator(func):
    "The most trivial (and common) decorator"
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)

def myfunc1(a, b, c='ok'):
    return (a, b, c)

myfunc2 = decorator(myfunc1)
myfunc3 = decorator(myfunc2)

即使是保守的look-before-you-leap 方法(检查函数参数规范)在这里也失败了,因为大多数装饰器的argspec 都是*args, **kwargs,而不管装饰的函数是什么。异常检查似乎也不可靠,因为myfunc.__name__ 将只是大多数装饰器的“包装器”,无论核心函数的名称如何。

如果函数可能有或可能没有装饰器,有什么好的解决方案吗?

【问题讨论】:

  • 答案:不要这样做。为什么要处理具有错误参数数量的函数的情况?我认为以不同的方式解决您的潜在问题会带来更好的解决方案。
  • 通过 C API 调用 python 函数时,如果能够捕获错误的参数错误,那就太好了。也就是说,当调用 pValue = PyObject_CallObject(pFunc, pArgs);有没有办法检测从 C++ 传递的参数与 Python 函数所期望的参数之间何时不匹配?现在,它只是立即返回,没有提示出了什么问题。
  • @user2787699:这是一个问题,不是评论。

标签: python exception try-catch decorator


【解决方案1】:

你可以这样做:

    try:
        myfunc()
    except IndexError:
        trace = sys.exc_info()[2]
        if trace.tb_next.tb_next is None:
            pass
        else:
            raise

虽然它有点丑,而且似乎违反了封装性。

从风格上讲,想要捕捉传递了太多参数似乎很奇怪。我怀疑对您正在做的事情进行更广泛的重新思考可能会解决问题。但如果没有更多细节,我无法确定。

编辑

可能的方法:检查你调用的函数是否有参数*args,**kwargs。如果是,假设它是一个装饰器并调整上面的代码以检查异常是否在更进一步的层中。如果不是,请按上述方式检查。

不过,我认为您需要重新考虑您的解决方案。

【讨论】:

【解决方案2】:

我不喜欢以这种方式施魔法。我怀疑你有一个潜在的设计问题。

--原来的答案和代码太不具体了问题被删除--

了解具体问题后编辑:

from inspect import getargspec

def can_call_effectively(f, args):
    (fargs, varargs, _kw, df) = getattr(myfunc, 'effective_argspec', \
        getargspec(myfunc))
    fargslen = len(fargs)
    argslen = len(args)
    minargslen = fargslen - len(df)
    return (varargs and argslen >= minargslen) or minargslen <= argslen <= fargslen

if can_call_effectively(myfunc, args)
    myfunc(*args)
else:
    fixit()

你所有的装饰器,或者至少是你想要透明的装饰器 通过上述代码调用,需要在返回的可调用对象上设置 'effective_argspec'。 非常明确,没有魔法。为此,您可以使用适当的代码装饰您的装饰器...

编辑:更多代码,透明装饰器的装饰器。

def transparent_decorator(decorator):
    def wrapper(f):
        wrapped = decorator(f)
        wrapped.__doc__ = f.__doc__
        wrapped.effective_argspec = getattr(f, 'effective_argspec', getargspec(f))
        return wrapped
    return wrapper

在你的装饰器上使用它:

@transparent_decorator
def decorator(func):
"The most trivial (and common) decorator"
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper  # line missing in example above

现在,如果您按上述方式创建 myfunc1 - myfunc3,它们将完全按预期工作。

【讨论】:

  • 我不知道你在这里的目的是什么。看来您希望我注释所有可能引发错误的代码,但如果我知道这一点,那么一开始就不会有错误......
  • 不,我只是想对不同的错误使用不同的异常,以便您可以在错误处理程序中轻松区分它们。不过,我可能误解了 exception_level(1),对于 myfunc 内部的异常,或者对于 myfunc 外部的异常但只是调用它,这应该是真的吗?
  • 我想捕获一个由使用错误参数调用函数的行为引起的异常,而不掩盖函数实现产生的错误。
  • 这很难,我怀疑是否有一个完全正确的解决方案而不涉及您的其他代码,因为装饰器生成的代码现在实际上是函数实现的一部分。我会尝试一种前瞻的变体。要么它必须通过遵循原始函数的内部属性来检查和处理所有使用过的装饰器(如果它在匿名闭包中,你就不走运了),或者你的所有(修改过的)装饰器都提供了 inspect.getargspec(origfunc) 的结果作为附加的包装器属性。我会尝试提供一些代码。
  • 几乎透明。 (是的,一年前的盗墓很有趣。)上面的透明装饰器实现目前与 doctest 不兼容。在wrapped = ... 之后可能需要wrapped.__doc__ = f.__doc__ 而且,堆栈跟踪中仍然有一个File "filename.py", line XX, in wrapper return func(*args, **kwargs) 用于调用@decorator fn 引发的异常。
【解决方案3】:

不幸的是,并非如此。最好的办法是反省返回的错误对象,看看是否提到了 myfunc 和参数的数量。

所以你会做这样的事情:

except TypeError, err:
    if err.has_some_property or 'myfunc' in str(err):
        fixit()
    raise

【讨论】:

  • myfunc 是一个变量,所以我必须使用 myfunc.__name__
【解决方案4】:

你可以这样做

>>> def f(x,y,z):
    print (f(0))


>>> try:
    f(0)
except TypeError as e:
    print (e.__traceback__.tb_next is None)


True
>>> try:
    f(0,1,2)
except TypeError as e:
    print (e.__traceback__.tb_next is None)


False

但更好的方法应该是计算函数的 args 数量并与预期的 args 数量进行比较

len(inspect.getargspec(f).args) != len (args)

【讨论】:

  • 这不是“更好”,它只是先看后跳的风格。这段代码在 99% 的情况下都会成功,所以最好不要检查。见:docs.python.org/glossary.html#term-lbyl
  • 请注意,exception.__traceback__ 是特定于 python3 的。
【解决方案5】:

您可以检索回溯并查看其长度。试试:

import traceback as tb
import sys

def a():
    1/0

def b():
    a()

def c():
    b()

try:
    a()
except:
    print len(tb.extract_tb(sys.exc_traceback))

try:
    b()
except:
    print len(tb.extract_tb(sys.exc_traceback))

try:
    c()
except:
    print len(tb.extract_tb(sys.exc_traceback))

打印出来

2
3
4

【讨论】:

    【解决方案6】:

    编写良好的包装器将保留它们包装的函数的函数名称、签名等;但是,如果您必须支持不支持的包装器,或者如果您想在包装器中捕获错误(不仅仅是最终包装的函数),那么没有通用的解决方案可以工作。

    【讨论】:

      【解决方案7】:

      我知道这是一篇旧帖子,但我偶然发现了这个问题,后来有了更好的答案。这个答案取决于 python 3 中的一个新特性,Signature objects

      使用该功能,您可以编写:

      sig = inspect.signature(myfunc)
      try:
          sig.bind(*args)
      except TypeError:
          return fixit()
      else:
          f(*args)
      

      【讨论】:

        【解决方案8】:

        在我看来,您正在尝试做的正是异常应该解决的问题,即异常将在调用堆栈中的某处被捕获,因此无需向上传播错误。

        相反,听起来您正在尝试以 C(非异常处理)方式进行错误处理,其中函数的返回值指示没有错误(通常为 0)或错误(非 0 值)。所以,我会尝试编写你的函数来返回一个值,并让调用者检查返回值。

        【讨论】:

        • 在调用函数失败的情况下返回值无关紧要ValueError("wrong number of arguments")
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-05-01
        • 2019-11-19
        • 2011-08-25
        • 2014-04-12
        • 1970-01-01
        • 1970-01-01
        • 2010-11-01
        相关资源
        最近更新 更多