如何以编程方式确保函数包含返回语句



如何验证函数是否包含return关键字?我经常忘记返回行,所以我担心我的包的用户在提供基于函数的输入时也会忘记。

def appler():
a = "apple"
# `return` is missing
def bananer():
b = "banana"
return b

我可以解析函数的实际代码字符串,最后一行包含"return",但这不是很健壮(可能是由注释触发的(。

def validate_funk(funk):
if condition_to_check_that_it_contains_rtrn:
pass
else:
raise ValueError(f"Yikes - The function you provided not contain a `return` statement:nn{funk}")
>>> validate_funk(appler)
#triggers ValueError
>>> validate_funk(bananer)
# passes

EDIT:理想情况下不运行函数。

您真正关心的可能不是返回语句本身,而是函数返回某种类型的东西。这可以通过使用类型提示(PEP484(最容易地实现:

def appler() -> str:
a = "apple"
# `return` is missing
def bananer() -> str:
b = "banana"
return b

现在,运行像mypy或Pyre(或许多其他(这样的静态分析工具:将发出关于函数appler中错误返回类型的警告(预期为str,实际为NoneType(。

寻找萨比克的答案,以获得更一般的答案。编写(单元(测试是另一种可以解决更多问题的好做法,如果做得好,这是对代码可维护性的投资。

没有return语句的函数默认返回None

>>> def abc():
pass
>>> print(abc())
None
>>> 

您可以使用以下选项添加支票:

def validate_func(function):
if function() == None:
raise ValueError("Yikes - Does not contain a `return` statement")

不过也有一些缺点。

  1. 您必须执行函数
  2. 如果您在函数中使用returnNone,它将不起作用

不太实用,但是的,这是一种方法。您还可以获得类中的局部函数列表或方法列表,并在它们之间循环,而不必单独检查每个函数。

对于所问的问题,ast模块将允许您进行检查。

然而,它本身似乎并不是很有用——正如其他人所指出的,没有return的函数是有效的(它返回None(,仅仅因为函数有return并不意味着它返回正确的值,甚至任何值(return可能在if语句中(。

有两种标准的处理方法:

  • 单元测试-单独的代码,使用各种输入组合(可能只有一个,可能有数百个(调用函数,并检查答案是否与手动计算的答案匹配,或者是否满足要求。

  • 检查CCD_ 15语句的思想的更一般的实现是";皮棉";,在Pythonpylint的情况下;它会查看您的代码,并检查各种看起来可能是错误的模式。一个附带的好处是它已经存在,它检查了几十种常见的模式。

  • 另一个不同的更通用的实现是mypy类型检查器;它不仅检查是否有return语句,而且还检查它是否返回了正确的类型,如函数头中所注释的那样。

通常;选通干线";发展过程;禁止手动更改main版本,并且只有通过测试、lint和/或mypy的更改才能被接受为main版本。

正如其他人所提到的,仅仅调用函数是不够的:return语句可能只存在于条件中,因此,需要传递特定的输入才能执行return语句。这也不是return存在的良好指标,因为它可能是returnNone,导致更大的模糊性。相反,可以使用inspectast模块:

测试功能:

def appler():
a = "apple"
# `return` is missing
def bananer():
b = "banana"
return b
def deeper_test(val, val1):
if val and val1:
if val+val1 == 10:
return 
def gen_func(v):
for i in v:
if isinstance(i, list):
yield from gen_func(i)
else:
yield i

inspect.getsource将函数的整个源作为字符串返回,然后可以将其传递给ast.parse。从那里,可以递归遍历语法树,搜索return语句的存在:

import inspect, ast
fs = [appler, bananer, deeper_test, gen_func]
def has_return(f_obj):
return isinstance(f_obj, ast.Return) or 
any(has_return(i) for i in getattr(f_obj, 'body', []))
result = {i.__name__:has_return(ast.parse(inspect.getsource(i))) for i in fs}

输出:

{'appler': False, 'bananer': True, 'deeper_test': True, 'gen_func': False}

具有定义的validate_funk:

def validate_funk(f):
if not has_return(ast.parse(inspect.getsource(f))):
raise ValueError(f"function '{f.__name__}' does not contain a `return` statement")
return True

注:

  1. 此解决方案不需要调用测试函数
  2. 解决方案必须在文件中运行。如果它在shell中运行,则会引发一个OSError。有关该文件,请参阅此Github Gist

您可以使用装饰器简化返回检查:

def ensure_return(func):
def wrapper(*args, **kwargs):
res = func(*args, **kwargs)
if res is None:
raise ValueError(f'{func} did not return a value')
return res
return wrapper
@ensure_return
def appler():
a = "apple"
# `return` is missing
@ensure_return
def bananer():
b = "banana"
return b

然后:

>>> appler()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in wrapper
ValueError: <function appler at 0x7f99d1a01160> did not return a value
>>> bananer()
'banana'

最新更新