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)