Preamble
Well, that is just for the challenge. Since, I agree, that looks like a XYProblem.
Plus, if it is just for convenience, to avoid having 2 functions, or, as in Torben's solution, an explicit parameter (like, if I understand correctly the intended usage async=True/False), you will probably understand that, even if you pushed the method I am about to show so far that it becomes reliable, that would be a way too heavy cost (both in development time, and in CPU time) to worth it.
All that negative preamble done, to do this kind of thing, you need the ability to inspect your own code. Which is theoretically possible in interpreted language. But very implementation dependent (implementation of the interpreter. CPython isn't the only python interpreter).
inspect module, for example, can inspect code. Can go upward in the call chain, and know what was the code that called a function.
Extremely wicked, shockingly evil and vile demo
import inspect
frames=[]
def drawPoint(x,y):
context = inspect.stack(context=1)[1].code_context[0]
if context.strip()[:9]=='drawPoint':
print("return value unused for", x, y)
else:
print("returning", x+y)
return x+y
drawPoint(1,0)
pt=drawPoint(10,20)
First call prints "return value unused" and returns nothing. Second returns x+y.
insect.stack gives me the current call stack. inspect.stack()[0] is the call to inspect.stack()itself.inspect.stack()[1]is the previous one in the call tree. That is the call todrawPoint. And inspect.stack()[1].code_context[0]is the line of code of that caller. That line is eitherdrawPoint(...)in the first call, andpt=drawPoint(...)` in the second.
So, what I do, is just inspecting a string containing some code, either drawPoint(1,0), pt=drawPoint(10,20), to try to deduce if return value is used.
Which I do in a very naive way.
Static check limitations (among zillions)
Counter-point (way to make my naive way fail)
print("wait for it",
drawPoint(15,25)
)
displays "return value unused for 15 25". Because line of code is drawPoint(15,25).
And the opposite
print('hello'); drawPoint(15,25)
For those two, of course, I could inspect 3 lines instead of 1 (that is the context=1 parameters. And the reason why I need that [0] after code_context: because there can be more than 1 string in what it returns), and parse ; (easier said than done). And then we can build some other counter-example with those... It would be pretty much writing a python parser to try to understand what is done with result.
In reality, we should either access an abstract tree representation of the code, or the virtual machine opcode, rather than the text version of the code. I am lazy (since I know that the whole point of this "answer" is to prove that it is a bad idea). But either way, to do this seriously, it would take way more complicated hacks than what I do here.
What is "used"?
And then next question would be: what of
pt = drawPoint(10,20)
# then do absolutely nothing with pt. Even garbage collect it
pt = None # just to ensure that after this point nobody use it
in such case, would you say that we use the result of the call or not? Up to what point would you want the optimization to go?
Point is, it is a fuzzy task anyway, since it is not well defined what is "result is used"; and it can be a very hard task to know it, whatever the definition of "used"
Dynamic analysis is needed
What of
def dosomething(callback):
callback(5,6)
dosomething(drawPoint)
And even more convulted way?
The only reliable way to know for sure if the value is used, is to simulate the rest of the code (so run it, basically, to predict future) to see if the result is used.
So, for your goal, you may be satisfied with some heuristic-conservative static criteria (when in doubt, let's be synchronized and return a value. But when I can statically prove that the call isn't using the result, no need to wait). But that static proof would probably cost you more CPU time than just wait. And the effort to write something serious would cost you way more than adding some returnValueNeeded=False
tl;dr
There are tools to inspect stack and call tree, and, generally speaking the code and the interpreter state. They are implementation dependent. And even the simplest reliable static inspection would be hard to implement. Theoretically, as long as you accept a ternary answer ("yes, return value is used", "no it is not used", "can't be sure"), those tools could be a starting point. But the only way to do it would be, at each call, to simulate future code execution. Clearly, that kind of code analysis could be doing for some invariant proving, or, for extreme debug mode logging, or for compiler optimization, ... It is certainly the kind of thing you do just to have some comfy "no need to pass a returnValueNeeded" for your coders.
asyncfunctions, which the caller can eitherawaitor chuck into a fire-and-forget background task.