Python - 使用异常引发进行参数检查

Python - Parameter checking with Exception Raising

我正在尝试将引发异常的代码块写入我的 Python 代码中,以确保传递给函数的参数满足适当的条件(即强制参数、类型检查参数、建立边界值用于参数等...)。我非常了解如何 manually raise exceptions as well as handling them.

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 语句,是否有更有效或更正确的方法来执行此任务?还是实施长时间的 if-then-else 语句是我唯一的选择?

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

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 TypeError("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

您也可以反转条件并使用 assert 而不是 if ...: raise,但由于 这些可能并不总是被执行。 您也可以扩展它以允许例如开放范围(如 (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")  # TypeError: Expected <class 'int'>, got [1, 2, 3]
foo(1, 2.,"e")  # ValueError: Should be in range (0.0, 1.0): 2.0

我觉得你可以用decorator来检查参数。

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'>

关于 Python 这件事困扰了我一段时间,如果提供的参数是 None 或缺少值,则没有标准的输出方式,也没有处理 JSON/Dict优雅地反对,

比如我想在报错信息中输出实参名,

username = None

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

没有办法传递实际的参数名称,除非你通过在错误输出消息中硬编码参数来人为地乱七八糟地这样做,即

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

但这既混乱又容易出现语法错误,而且维护起来很麻烦。

为此,我写了一个"Dictator"函数,

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

如果您将参数添加到字典中,或者从 YAML 或 JSON 配置文件中读取参数,您可以告诉 Dictator 在参数为 null 时引发 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 值并在错误消息中输出实际参数名称