【问题标题】:How to programmatically ensure that a function includes a return statement?如何以编程方式确保函数包含 return 语句?
【发布时间】:2021-04-13 12:34:03
【问题描述】:

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

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

def bananer():
    b = "banana"
    return b

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

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:\n\n{funk}")
>>> validate_funk(appler)
#triggers ValueError

>>> validate_funk(bananer)
# passes

编辑:理想情况下不运行该函数。

【问题讨论】:

  • 当您为代码/函数编写单元测试时,这不会成为问题
  • 其中一些可以帮助您避免犯错吗? blog.codacy.com/which-python-static-analysis-tools-should-i-use
  • funk 是用户提供的参数。
  • 顺便说一句,问题中的两个函数都很简单,它们可能应该跳过局部变量;只是:def appler(): return "apple"

标签: python function


【解决方案1】:

你真正关心的可能不是return语句本身,而是函数返回某种类型的something。您可以使用type hints (PEP 484) 轻松完成此操作:

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

def bananer() -> str:
    b = "banana"
    return b

现在,像 mypy 或 Pyre(或许多其他)一样运行 static analysis tool:将在函数 appler 中发出错误返回类型的警告(预期 str,得到 NoneType)。

寻找 sabik 的答案以获得更一般的答案。编写(单元)测试是另一种很好的做法,它可以发现更多问题,而且如果做得好,是对代码可维护性的投资。

【讨论】:

    【解决方案2】:

    没有返回语句的函数默认返回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. 如果你在函数中 returning None 它将不起作用

    不太实用,但是是的,这是一种方法。您还可以获取a list of local functionsa list of methods in a class 并循环访问它们,而无需单独检查每个函数。

    【讨论】:

    • 这些函数中的大多数不应返回None,因此这是朝着正确方向迈出的重要一步
    • 是的,我的 fn_train 训练一个神经网络,需要大量疯狂的输入,所以这是不行的。
    【解决方案3】:

    对于提出的问题,ast 模块将让您检查。

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

    有几种标准的处理方法:

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

    • 在 Python pylint 的情况下,检查 return 语句的想法的更一般的实现是“lint”;它会查看您的代码并检查看起来可能是错误的各种模式。一个附带的好处是它已经存在并且它检查了许多常见的模式。

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

    通常,这些将与“封闭式主干”开发过程一起使用;禁止手动更改 main 版本,只有通过测试、lint 和/或 mypy 的更改才会被 main 版本接受。

    【讨论】:

    • 这只是为了在用户提供函数时捕捉'doh forgot to return'。酷,现在看这些。赞成。如果您编辑 w 示例,我将接受。
    • 如果您在任何情况下调用用户提供的函数,您都可以在此时检查None返回值,或者检查返回值是否有效(例如,如果它是一个百分比,它应该是一个介于 0 和 100 之间的数字)。这也会捕获许多其他错误;毕竟,你并不关心函数是如何实现的,只要它返回一个可用的值......
    • 例如,用户可以提供一个实现为lambda的函数,它不需要return关键字来返回值。
    • 我在这里看到了,但不确定如何隔离调用它的方法或者是否可以mypy.readthedocs.io/en/stable/…
    • 这取决于更广泛的背景。如果你的代码被用户交给了一个你需要调用的函数,最直接的方法是在你调用它之后检查返回值,如果它不可接受则给出错误(例如,如果它是 None 和这是无效的)。如果你想检查你自己的代码,那么你可以在整个代码库上运行 pylint 或 mypy 并检查它是否没有报告任何方法中的任何可疑内容。如果代码属于一个组或团队,那么设置一个带有自动化测试、pylint 和/或 mypy 的封闭式主干可能是要走的路。
    【解决方案4】:

    正如其他人所提到的,仅仅调用函数是不够的:return 语句可能只出现在条件中,因此,需要传递特定的输入才能执行 return 语句。这也不是return 存在的良好指标,因为它可能return None,导致更大的歧义。相反,可以使用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。有关文件,请参阅 thisGithub Gist。

    【讨论】:

      【解决方案5】:

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

      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'
      

      【讨论】:

        猜你喜欢
        • 2019-06-19
        • 2018-05-18
        • 1970-01-01
        • 1970-01-01
        • 2011-01-18
        • 2016-04-24
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多