适用于许多条件的 Python 设计模式



编写具有许多条件的验证函数的推荐结构是什么?请参阅这两个示例。第一个看起来很丑,第二个不是很常见,也许是因为assert通常用于排除意外行为。有更好的选择吗?

def validate(val):
if cond1(val):
return False
if cond2(val):
return False
if cond3(val)
return False
return True

def validate(val):
try:
assert cond1(val)
assert cond2(val)
assert cond3(val)
return True
except AssertionError:
return False

编写该函数的一种紧凑方法是使用any和生成器表达式:

def validate(val):
conditions = (cond1, cond2, cond3)
return not any(cond(val) for cond in conditions)

anyall功能会短路,因此一旦有确定的结果,它们就会停止测试,即一旦达到 True-ish 值any就会停止,all一旦达到 False-ish 值就停止,因此这种形式的测试非常有效。

我还应该提到,将这样的生成器表达式传递给all/any比将列表理解更有效。因为all/any一旦它们得到有效结果就会停止测试,如果你从生成器馈送它们,那么生成器也会停止,因此在上面的代码中,如果cond(val)计算结果为 True-ish 值,则不会测试其他条件。但是,如果您通过all/any列表理解,例如any([cond(val) for cond in conditions])必须在all/any开始测试之前构建整个列表。


您没有向我们展示您的cond职能的内部结构,但您在问题中确实提到了assert,所以我认为以下评论在这里是有序的。

正如我在评论中提到的,assert不应该用于验证数据,而是用于验证程序逻辑。(此外,可以通过 -O 命令行选项禁用断言处理)。用于具有无效值的数据的正确异常是ValueError,对于类型错误的对象,请使用TypeError。但请记住,例外旨在处理异常情况。

如果您预计会出现大量格式不正确的数据,那么使用基于if的逻辑通常比使用异常更有效。如果实际上没有引发异常,Python 异常处理非常快,事实上它比等效的基于if的代码更快。但是,如果引发异常的时间超过 5-10%,则基于try...except的代码将明显慢于基于if的代码。

当然,有时使用例外是唯一明智的选择,即使情况并不是那么特殊。一个典型的示例是,将数字字符串集合转换为实际的数字对象时,表示整数的字符串将转换为整数对象,其他数字字符串将转换为浮点数,其他字符串将保留为字符串。在 Python 中执行此操作的标准方法涉及使用异常。例如:

def convert(s):
''' Convert s to int or float, if possible '''
try:
return int(s)
except ValueError:
try:
return float(s)
except ValueError:
return s
data = ['42', 'spam', '2.99792458E8']
out = [convert(u) for u in data]
print(out)
print([type(u) for u in out])

输出

[42, 'spam', 299792458.0]
[<class 'int'>, <class 'str'>, <class 'float'>]

在这里使用"先看后跳"逻辑是可能的,但它使代码更加复杂,因为您需要处理可能的减号和科学记数法。

def valid(value):
return (is_this(value)
and that(value)
and other(value))

and运算符在 Python 中表现出"短路"行为。

第一种方法要好得多。可以使用any()
对其进行美化

def validate_conditions(value):
return not any((condition(value) for condition in conditions))

最新更新