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.
CodePudding user response:
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)
CodePudding user response:
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.
CodePudding user response:
If you don't want to change all the uses e.g. re = foo()
, then it's better to keep foo
returning 1 non-tuple value.
You should have your new usage in a different function: def _foo2(): return ('bar', 'baz')
.
You can also edit foo
to be dependent on _foo2
without changing the usage in the rest of your code: def foo(): return _foo2()[0]
. foo
then becomes somewhat of an abstraction. The rest of your code can depend on foo
having a particular behavior. The real work is done elsewhere e.g. _foo2
and can change at anytime, and all you would need to do is edit foo
a little.