什么是未满足前提条件的正确异常?

3

在函数中,如果要表示前置条件未满足,应该引发哪种适当的异常?

示例:

def print_stats(name, age):
    if name is None:
        raise Exception("name cannot be None")
    if not type(name) is str:
        raise Exception("name must be a string")

    if age is None:
        raise Exception("age cannot be None")
    if age < 0:
        raise Exception("age cannot be negative")

    print("{0} is {1} years old".format(name, age))
4个回答

6
你应该同时使用TypeErrorValueError
前三个异常应该是TypeError,因为我们要表明参数的类型不正确。从文档中可以看到:

exception TypeError

当操作或函数应用于不适当类型的对象时引发。相关值是一个字符串,给出了有关类型不匹配的详细信息。

然而,最后一个异常应该是ValueError,因为age是正确的类型,但具有不正确的值(它是负数)。从文档中可以看到:

exception ValueError

当内置操作或函数接收到一个类型正确但值不合适的参数,并且情况没有被更精确的异常(如IndexError)描述时引发。


出于好奇,TypeError 适用于“将操作或函数应用于不合适类型的对象”。严格来说 - 在执行操作时,更倾向于将其视为 TypeError(“让它失败”),还是将其视为验证错误(ValueError)?我正在看短语“操作或函数接收参数”与“将操作或函数应用于对象”,如果这有意义的话。(我理解在这种特定情况下 None 是错误的类型。) - Curtis Mattoon
1
ValueError 不是一个“验证错误”。这更像是一个 AssertionErrorTypeErrorValueError 之间的区别仅仅是一个问题:“不适当的对象是否具有正确的类型?”如果是,就引发一个 ValueError。否则,引发一个 TypeError。这两个异常基本上是相同的东西;它们都意味着我们遇到了一个不适当的对象。 - user2555451

2
我认为你应该使用TypeErrorValueError,但你也可以改进一下你应用前置条件的方法。
有一段时间我在尝试着使用后置条件和前置条件。Python允许你使用装饰器写出比函数内部的那些if语句更加优雅的解决方案。
例如:
def precondition(predicate, exception, msg):        // 1
    def wrapper(func):
        def percond_mechanism(*args, **kwargs):     // 2
            if predicate(*args, **kwargs):
                return func(*args, **kwargs)        // 3
            else:
                raise exception(msg)                // 4
        return percond_mechanism
    return wrapper
  1. 条件,如果条件不满足想要抛出的异常和想要显示的消息。
  2. 这部分检查是否满足条件。
  3. 如果一切正常,只需返回原始函数的结果。
  4. 如果不行,则使用传递的异常和消息引发异常。

现在您可以像这样编写函数:

@precondition(lambda name, age: name is not None, ValueError, "name can't be None")
@precondition(lambda name, age: type(name) is str, TypeError, "name has to be str")
# You can continue adding preconditions here.
def print_stats(name, age):
    print("{0} is {1} years old".format(name, age))

这种方式更容易阅读哪些操作可以做,哪些不能做。实际上,您可以在任何想要使用的函数中使用 precondition 装饰器。


0

我喜欢 Raydel Miranda's answer 使用函数装饰器前置条件。这里有一种类似的方法,它不使用装饰器,而是使用内省和 eval。它可能不太高效,但可以说更加简洁和表达力强。

import inspect

class ValidationError(ValueError):
    pass

def validate(assertion, exc=ValidationError, msg=''):
    """
    Validate the given assertion using values
    from the calling function or method. By default,
    raises a `ValidationException`, but optionally
    raises any other kind of exeception you like.
    A message can be provided, and will be formatted
    in the context of the calling function. If no
    message is specified, the test assertion will be
    recapitulated as the cause of the exception.
    """
    frame = inspect.currentframe().f_back
    f_locals, f_globals = frame.f_locals, frame.f_globals
    result = eval(assertion, f_globals, f_locals)
    if result:
        return
    else:
        if msg:
            msg = msg.format(**f_locals)
        else:
            msg = 'fails test {0!r}'.format(assertion)
        raise(exc(msg))

def r(name):
    validate('isinstance(name, str)', msg='name must be str (was {name!r})')
    validate('name.strip() != ""',    msg='name must be non-empty (was {name!r})')
    print(name,)

def r2(name, age):
    validate('isinstance(name, str)', TypeError,  'name must be str (was {name!r})')
    validate('name.strip() != ""',    ValueError, 'name must be non-empty (was {name!r})')
    validate('isinstance(age, int)',  TypeError,  'age must be int (was {age!r})')
    validate('age >= 0',              ValueError, 'age must be non-negative (was {age!r})')
    print(name,)

r('Joe')
r('')
r2('Dale', -3)
r2('Dale', None)

这将引发诸如以下的异常:

ValidationError: name must be non-empty (was '')

还有一个好处:如果您没有指定任何消息,它仍然会给出合理的输出。例如:

def r2simple(name, age):
    validate('isinstance(name, str)')
    validate('name.strip() != ""')
    validate('isinstance(age, int)')
    validate('age >= 0')
    print(name,)

r2simple('Biff', -1)

返回:

ValidationError: fails test 'age >= 0'

这将在Python 2或3下工作。


0

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