Pili Hu
Pili Hu

Reputation: 151

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

There were several discussions on "returning multiple values in Python", e.g. 1, 2. This is not the "multiple-value-return" pattern I'm trying to find here. No matter what you use (tuple, list, dict, an object), it is still a single return value and you need to parse that return value (structure) somehow.

The real benefit of multiple return value is in the upgrade process. For example,

originally, you have

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

Then you decided that func() can return some extra information but you don't want to break previous code (or modify them one by one). It looks like

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)

The previous codes (func() + func()) are broken. You have to fix it.

I don't know whether I made the question clear... You can see the CLISP example. Is there an equivalent way to implement this pattern in Python?

EDIT: I put the above clisp snippets online for your quick reference.


Let me put two use cases here for multiple return value pattern. Probably someone can have alternative solutions to the two cases:


Current alternatives: (they are not "multi-value-return" constructions, but they can be engineering solutions that satisfy some of the points listed above)

  1. tuple, list, dict, an object. As is said, you need certain parsing from the client side. e.g. if ret.success == True: blabla. You need to ret = func() before that. It's much cleaner to write if func() == True: blabal.
  2. Use Exception. As is discussed in this thread, when the "False" case is rare, it's a nice solution. Even in this case, the client side code is still too heavy.
  3. Use an arg, e.g. def func(main_arg, detail=[]). The detail can be list or dict or even an object depending on your design. The func() returns only original simple value. Details go to the detail argument. Problem is that the client need to create a variable before invocation in order to hold the details.
  4. Use a "verbose" indicator, e.g. def func(main_arg, verbose=False). When verbose == False (default; and the way client is using func()), return original simple value. When verbose == True, return an object which contains simple value and the details.
  5. Use a "version" indicator. Same as "verbose" but we extend the idea there. In this way, you can upgrade the returned object for multiple times.
  6. Use global detail_msg. This is like the old C-style error_msg. In this way, functions can always return simple values. The client side can refer to detail_msg when necessary. One can put detail_msg in global scope, class scope, or object scope depending on the use cases.
  7. Use generator. yield simple_return and then yield detailed_return. This solution is nice in the callee's side. However, the caller has to do something like func().next() and func().next().next(). You can wrap it with an object and override the __call__ to simplify it a bit, e.g. func()(), but it looks unnatural from the caller's side.
  8. Use a wrapper class for the return value. Override the class's methods to mimic the behaviour of original simple return value. Put detailed data in the class. We have adopted this alternative in our project in dealing with bool return type. see the relevant commit: https://github.com/fqj1994/snsapi/commit/589f0097912782ca670568fe027830f21ed1f6fc (I don't have enough reputation to put more links in the post... -_-//)

Here are some solutions:

Upvotes: 0

Views: 2675

Answers (3)

yupbank
yupbank

Reputation: 265

try inspect?

i did some try, and not very elegant, but at least is doable.. and works :)

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             

Upvotes: 1

ZhiQiang Fan
ZhiQiang Fan

Reputation: 1042

the magic is you should use design pattern blablabla to not use actual operation when you process the result, but use a parameter as the operation method, for your case, you can use the following code:

def x():
    #return 1
    return 1, 'x'*1

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

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

if you want generic solution for more complicated situation, you can extend the f function, and specify the process operation via the op parameter

Upvotes: 0

DhruvPathak
DhruvPathak

Reputation: 43235

Your case does need code editing. However, if you need a hack, you can use function attributes to return extra values , without modifying return values.

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)

Demo : http://codepad.org/0hJOVFcC

However, be aware that function attributes will behave like static variables, and you will need to assign values to them with care, appends and other modifiers will act on previous saved values.

Upvotes: 0

Related Questions