Python 中的多值返回模式(不是元组、列表、字典或对象解决方案)


有几个

关于"在 Python 中返回多个值"的讨论,例如1,2.这不是我试图在这里找到的"多值返回"模式。无论你使用什么(元组、列表、字典、对象(,它仍然是单个返回值,你需要以某种方式解析该返回值(结构(。

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

最初,你有

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

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

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 片段放到网上供您快速参考。


让我在这里为多个返回值模式提供两个用例。可能有人可以对这两种情况有替代解决方案:

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

当前的替代方案:(它们不是"多价值回报"结构,但它们可以是满足上面列出的一些要点的工程解决方案(

  1. 元组、列表、字典、对象。如前所述,您需要从客户端进行某些解析。例如 if ret.success == True: blabla .在此之前,您需要ret = func()。写if func() == True: blabal要干净得多.
  2. 使用Exception .正如此线程中所讨论的,当"False"情况很少见时,这是一个很好的解决方案。即使在这种情况下,客户端代码仍然太重。
  3. 使用参数,例如 def func(main_arg, detail=[]) .根据您的设计,detail可以是list的,也可以是dict的,甚至是对象。func()仅返回原始简单值。详细信息转到detail参数。问题是客户端需要在调用之前创建一个变量才能保存详细信息。
  4. 使用"详细"指示器,例如 def func(main_arg, verbose=False) .当verbose == False(默认值;以及客户端使用 func() 的方式(时,返回原始简单值。verbose == True 时,返回一个包含简单值和详细信息的对象。
  5. 使用"版本"指示器。与"冗长"相同,但我们在那里扩展了这个想法。这样,您可以多次升级返回的对象。
  6. 使用全局detail_msg 。这就像旧的 C 式error_msg.这样,函数始终可以返回简单值。必要时,客户端可以参考detail_msg。可以根据用例将detail_msg放在全局范围、类范围或对象范围中。
  7. 使用生成器。 yield simple_return然后yield detailed_return.这个解决方案在被叫方方面很好。但是,调用方必须执行类似 func().next()func().next().next() 。你可以用一个对象包装它并覆盖__call__以简化它,例如 func()(),但从呼叫者的角度来看,它看起来不自然。
  8. 对返回值使用包装类。重写类的方法以模拟原始简单返回值的行为。将详细数据放在类中。我们在处理返回类型的项目中采用了bool替代方法。查看相关提交:https://github.com/fqj1994/snsapi/commit/589f0097912782ca670568fe027830f21ed1f6fc(我没有足够的声誉在帖子中放置更多链接... -_-//(

以下是一些解决方案:

  • 根据@yupbank的回答,我把它形式化为装饰器,见github.com/hupili/multiret
  • 上面的第 8 个备选方案说我们可以包装一个类。这是我们目前采用的工程解决方案。为了包装更复杂的返回值,我们可能会使用 meta 类按需生成所需的包装类。没有尝试过,但这听起来像是一个强大的解决方案。

尝试检查?

我做了一些尝试,不是很优雅,但至少是可行的......并且:)

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             

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

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

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

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

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 参数指定过程操作

最新更新