关于函数:Python中的多值返回模式(不是元组、列表、dict或对象解决方案)

Multiple Value Return Pattern in Python (not tuple, list, dict, or object solutions)

有几个关于"在python中返回多个值"的讨论,例如1,2。这不是我想在这里找到的"多值返回"模式。无论您使用什么(元组、列表、dict、对象),它仍然是一个返回值,您需要以某种方式解析该返回值(结构)。

多个返回值的真正好处是在升级过程中。例如,

原来,你有

1
2
3
def func():
    return 1
print func() + func()

然后您决定func()可以返回一些额外的信息,但是您不想破坏以前的代码(或者逐个修改它们)。看起来像

1
2
3
4
5
6
def func():
    return 1,"extra info"
value, extra = func()
print value # 1 (expected)
print extra # extra info (expected)
print func() + func() # (1, 'extra info', 1, 'extra info') (not expected, we want the previous behaviour, i.e. 2)

以前的代码(func() + func()已损坏。你得把它修好。

我不知道我是否把问题讲清楚了…您可以看到clisp示例。在python中是否有一种实现此模式的等效方法?

编辑:我把上面的clisp片段放到网上,供你快速参考。

让我在这里为多返回值模式放置两个用例。可能有人可以对这两种情况有其他解决方案:

  • 更好的支持平滑升级。这在上面的示例中显示。
  • 具有更简单的客户端代码。请参阅我目前为止的以下替代解决方案。使用异常可以使升级过程顺利进行,但需要花费更多的代码。

当前的替代方案:(它们不是"多值回报"结构,但它们可以是满足上述某些点的工程解决方案)

  • tuple,list,dict,一个对象。如前所述,您需要从客户端进行一定的解析。如if ret.success == True: blabla。在那之前,你需要先完成1(3)。写if func() == True: blabal要干净得多。
  • 使用Exception。正如本文所讨论的,当"错误"情况很少出现时,这是一个很好的解决方案。即使在这种情况下,客户端代码仍然太重。
  • 使用arg,例如def func(main_arg, detail=[])。根据您的设计,detail可以是listdict甚至是一个物体。func()只返回原始简单值。详情请参阅detail论点。问题是,客户机需要在调用之前创建一个变量,以便保存详细信息。
  • 使用"详细"指示器,例如def func(main_arg, verbose=False)。当verbose == False时(默认;客户机使用func()的方式),返回原始简单值。当verbose == True时,返回一个包含简单值和细节的对象。
  • 使用"版本"指示器。与"详细"相同,但我们在这里扩展了这个概念。这样,您可以多次升级返回的对象。
  • 使用global detail_msg。这就像旧的C型error_msg。这样,函数总是可以返回简单的值。必要时,客户方可参考detail_msg。根据用例的不同,可以将detail_msg放在全局范围、类范围或对象范围中。
  • 使用发电机。yield simple_return,然后是yield detailed_return。这个解决方案在被叫方方面很好。但是,打电话的人必须做一些类似于func().next()func().next().next()的事情。您可以用一个对象包装它,并重写__call__,以稍微简化它,例如func()(),但从调用者的角度来看,它看起来不自然。
  • 使用包装类作为返回值。重写类的方法以模拟原始简单返回值的行为。将详细数据放入类中。我们在处理bool返回类型的项目中采用了这种替代方案。参见相关承诺:https://github.com/fqj1994/snsapi/commit/589f0097912782ca670568fe027830f21ed1f6fc(我没有足够的声誉在帖子中添加更多链接…-- / /)
  • 以下是一些解决方案:

    • 根据@yupbank的回答,我把它形式化为一个装饰,见github.com/hupili/multiret
    • 上面第八个选项说我们可以结束一个课程。这是我们目前采用的工程解决方案。为了包装更复杂的返回值,我们可以使用元类根据需要生成所需的包装类。还没有尝试过,但这听起来是一个强有力的解决方案。


    试试看吗?

    我做了一些尝试,但不太优雅,但至少是可行的。作品:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    import inspect                                                                    
    from functools import wraps                                                      
    import re


    def f1(*args):                                                                    
      return 2                                                                      

    def f2(*args):                                                                    
      return 3, 3                                                                  

    PATTERN = dict()                                                                  
    PATTERN[re.compile('(\w+) f()')] = f1                                            
    PATTERN[re.compile('(\w+), (\w+) = f()')] = f2                                    

    def execute_method_for(call_str):                                                
      for regex, f in PATTERN.iteritems():                                          
        if regex.findall(call_str):                                              
            return f()                                                            

    def multi(f1, f2):                                                                
      def liu(func):                                                                
        @wraps(func)                                                              
        def _(*args, **kwargs):                                                  
            frame,filename,line_number,function_name,lines,index=\                
                inspect.getouterframes(inspect.currentframe())[1]                
            call_str = lines[0].strip()                                          
            return execute_method_for(call_str)                                  
        return _                                                                  
    return liu                                                                    

    @multi(f1, f2)                                                                    
    def f():                                                                          
      return 1                                                                      



    if __name__ == '__main__':                                                        
      print f()                                                                    
      a, b = f()                                                                    
      print a, b


    神奇的是,在处理结果时,您应该使用设计模式blablablabla而不是实际操作,而是使用一个参数作为操作方法,对于您的情况,可以使用以下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    def x():
        #return 1
        return 1, 'x'*1

    def f(op, f1, f2):
        print eval(str(f1) + op + str(f2))

    f('+', x(), x())

    如果需要更复杂情况的通用解决方案,可以扩展f函数,并通过op参数指定流程操作。


    您的案例需要进行代码编辑。但是,如果您需要一个hack,您可以使用函数属性返回额外的值,而不修改返回值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    def attr_store(varname, value):
        def decorate(func):
            setattr(func, varname, value)
            return func
        return decorate

    @attr_store('extra',None)
    def func(input_str):
        func.extra = {'hello':input_str  +" ,How r you?", 'num':2}
        return 1



    print(func("John")+func("Matt"))
    print(func.extra)

    演示:http://codepad.org/0hjovfcc

    但是,请注意,函数属性的行为将类似于静态变量,您需要小心地为它们分配值,附录和其他修饰符将作用于以前保存的值。