Node.JS
Node.JS

Reputation: 1592

Non-invasive way to return multiple values from a function?

I am dealing with a large code base and I am wondering what is a non-invasive way to add an extra return value to a function without changing all the uses. For example:

Existing setup:

def foo():
    return 'bar'

re = foo()

My attempt:

def foo():
    return 'bar', 'baz'


re, _ = foo()  # re has a correct value but I had to add `, _` in all uses
re    = foo()  # Error: re is now a tuple, thus semantic has changed. 

Upvotes: 3

Views: 124

Answers (2)

juanpa.arrivillaga
juanpa.arrivillaga

Reputation: 96277

It is very important to understand that you can only ever return a single value, that value may be a container, like a tuple, like in your second example. But it is always a single type.

If you change the return value, then you can't expect to not have downstream effects, unless you change the value to some LSP compliant subtype. In this case, you could return some string subtype with an extra attribute, if you don't want to break the downstream uses.

class StringWithMetadata(str):
    def __new__(cls, obj, metadata=None):
        return super().__new__(cls, obj)
    def __init__(self, obj, metadata=None):
        # note, ignoring obj
        self.metadata = metadata # or whatever appropriate name you want


def foo():
    return StringWithMetadata('bar', 'baz')

re = foo()
print(re, re.metadata)

Edit:

Seeing as you tagged this with Python 2.7 (you really should avoid this if you can), then you can either use the longform of super:

return super(StringWithMetadata, cls).__new__(cls, obj)

Or just the explicit way (which you could always do):

return str.__new__(cls, obj)

Upvotes: 4

Pierre D
Pierre D

Reputation: 26271

If the intent is to add instrumentation to a function that has a a relatively large usage footprint (i.e., without changing all the code that uses that function), perhaps something along those lines might help:

def foo(spy=None):
    result = 'bar'
    if isinstance(spy, dict):
        spy['ret'] = result
        spy['other'] = 'baz'
        # ...
    return result

Examples:

The original uses can remain unchanged:

r = foo()
>>> r
'bar'

But you can also pass a dict to obtain some other info from within your function:

spy = {}
r2 = foo(spy=spy)
>>> spy
{'ret': 'bar', 'other': 'baz'}

You can of course use other types for your "spy" container.

Upvotes: 1

Related Questions