Python - 使用异常提升进行参数检查

时间:2016-07-28 13:58:18

标签: python if-statement exception typechecking

我试图将异常提升代码块编写到我的Python代码中,以确保传递给函数的参数满足适当的条件(即使参数成为必需,类型检查参数,建立参数的边界值等等。 )。我很满意地理解如何context manager以及manually raise exceptions

from numbers import Number

def foo(self, param1 = None, param2 = 0.0, param3 = 1.0):
   if (param1 == None):
      raise ValueError('This parameter is mandatory')
   elif (not isinstance(param2, Number)):
      raise ValueError('This parameter must be a valid Numerical value')
   elif (param3 <= 0.0):
      raise ValueError('This parameter must be a Positive Number')
   ...

这是Python中可接受的(经过验证的)参数检查方式,但我不得不怀疑:由于Python除了if-then-else语句之外没有编写Switch-case的方法,因此效率更高或正确的方法来执行此任务?或者正在实施if-then-else语句的长篇是我唯一的选择?

3 个答案:

答案 0 :(得分:2)

您可以创建装饰器函数并将期望类型和(可选)范围作为参数传递。像这样:

def typecheck(types, ranges=None):
    def __f(f):
        def _f(*args, **kwargs):
            for a, t in zip(args, types):
                if not isinstance(a, t):
                    raise ValueError("Expected %s got %r" % (t, a))
            for a, r in zip(args, ranges or []):
                if r and not r[0] <= a <= r[1]:
                    raise ValueError("Should be in range %r: %r" % (r, a))
            return f(*args, **kwargs)
        return _f
    return __f

而不是if ...: raise您也可以反转条件并使用assert,但作为noted in comments,这些可能并不总是被执行。 你也可以扩展它以允许例如开放范围(如(0., None))或接受任意(lambda)函数以进行更具体的检查。

示例:

@typecheck(types=[int, float, str], ranges=[None, (0.0, 1.0), ("a", "f")])
def foo(x, y, z):
    print("called foo with ", x, y, z)

foo(10, .5, "b")  # called foo with  10 0.5 b
foo([1,2,3], .5, "b")  # ValueError: Expected <class 'int'>, got [1, 2, 3]
foo(1, 2.,"e")  # ValueError: Should be in range (0.0, 1.0): 2.0

答案 1 :(得分:1)

我认为您可以使用装饰器来检查参数。

def parameterChecker(input,output):
...     def wrapper(f):
...         assert len(input) == f.func_code.co_argcount
...         def newfun(*args, **kwds):
...             for (a, t) in zip(args, input):
...                 assert isinstance(a, t), "arg {} need to match {}".format(a,t)
...             res =  f(*args, **kwds)
...             if not isinstance(res,collections.Iterable):
...                 res = [res]
...             for (r, t) in zip(res, output):
...                 assert isinstance(r, t), "output {} need to match {}".format(r,t)   
...             return f(*args, **kwds)
...         newfun.func_name = f.func_name
...         return newfun
...     return wrapper

example:
@parameterChecker((int,int),(int,))
... def func(arg1, arg2):
...     return '1'
func(1,2)
AssertionError: output 1 need to match <type 'int'>

func(1,'e')
AssertionError: arg e need to match <type 'int'>

答案 2 :(得分:0)

这一直困扰着我一段时间关于Python,如果提供的param为None或缺少值,则没有标准的输出方法,也没有优雅地处理JSON / Dict对象,

例如我想在错误消息中输出实际参数名称

username = None

if not username:
    log.error("some parameter is missing value")

没有办法传递实际的参数名称,除非你通过在错误输出消息中对参数进行硬编码来人为地弄乱,即

if not username:
    log.error("username is missing value")

但这既麻烦又容易出现语法错误,并且难以维持。

出于这个原因,我写了一个“独裁者”功能,

https://medium.com/@mike.reider/python-dictionaries-get-nested-value-the-sane-way-4052ab99356b

如果将参数添加到dict中,或者从YAML或JSON配置文件中读取参数,如果param为null,则可以告诉Dictator引发ValueError,

例如,

config.yaml

skills:
  sports:
    - hockey
    - baseball

现在你的py程序读入这个配置文件和参数,作为JSON字典,

with open(conf_file, 'r') as f:
    config = yaml.load(f)

现在设置你的参数,并检查它们是否为NULL

sports = dictator(config, "skills.sports", checknone=True)

如果sports为None,则会引发ValueError,告诉您确切缺少哪个参数

ValueError("missing value for ['skills']['sports']")

你也可以为你的参数提供一个后备值,所以如果它是None,给它一个默认的后备值,

sports = dictator(config, "skills.sports", default="No sports found")

这可以避免丑陋的Index / Value / Key错误异常。

它是一种灵活,优雅的方式来处理大型字典数据结构,还使您能够检查程序的Null值参数并在错误消息中输出实际参数名称