3

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. 
3
  • 2
    If you change the return type of a function, you're going to have to change its usage everywhere it's used. Commented Aug 18, 2021 at 20:33
  • 2
    You can only ever return a single value, that value may be a container, like a tuple, like in your second example. Im not exactly sure what you are expecting. If you change the return value, then you can't expect to not have downstream effects, unless you change the value with some LSP compliant subtype Commented Aug 18, 2021 at 20:34
  • 5
    If you're making a breaking change to the return type of a function that others are using perhaps you should create a new function and optionally add a deprecation warning to the old function to allow others to migrate Commented Aug 18, 2021 at 20:38

2 Answers 2

4

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)
Sign up to request clarification or add additional context in comments.

2 Comments

nice technique!
@PierreD yes, to be honest though, this is about the only use-case I would tolerate for a string subclass - i.e. just a string with some extra attributes of metadata. Honestly, this is just a bandaid over a design that was not adequately encapsulated, but that happens.
1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.