I’d really love a no_discard or a must_use decorator.
I’ve seen multiple times in our codebases that there were bugs because people were working with immutable data and in one place, instead of using state = state.with_xyz(5) they had just state.with_xyz(5).
I don’t really think it’s worth making, as users can basically do anything with functions.
For example I sometimes did
try:
int(val)
except ValueError: ...
which is a totally valid use case IMO.
Also, when we were to do x = function_with_no_discard(), what guarantees whetherx is used later on or not. And even there it could again be dynamic (using exec, manipulating the namespace, using it conditionally, …).
We’ll never know wether to throw an error or not.
If we, for example do func(function_with_no_discard(), …) some random (keyword) argument being given can influence wether the return value is actually used. Should checkers really go that deep into the function (perhaps even more ones later on) just to check that? What if the result is only used if some random API, returns something specific.
Python, generally is way to dynamic for type checkers to not have to guess sometimes. But introducing even more cases where they’d have to guess doesn’t make it better.
And those making that kind of error a lot of times are, mostly beginners. Once you use type-checkers, you’ll automatically know wether the return value is None or not.
If the result is assigned to a variable, even if it _, or if the variable is never used, that’s still counted as used. If x isn’t used, you’ll probably get another warning about an unused variable so there is no need for duplicate warnings. If you assigned it to _ that shows you made a conscience effort to discard it so it’s probably fine.
I get this problem each time I require to remind myself if the function I call is an in-place modifier instead of a transform-and-return function. But that latter case is the regular case and the problem arises when an in-place modificator is involved.
One of these statements is nonsense, I can never remember which one :
list2 = sort(list1)
list3 = list1.sorted()
Imo the special rule should be stated on the ‘invalid return’ rather than on the ‘nodiscard’ one.
In this regard, this topic is related : "Wrong" Special Form
Agreed, and I want to underline that I really don’t think you need to invent an esoteric use-case to arrive at common real-world examples that this could help with.
This exact goof-up still occasionally bites me, particularly if I’ve just come from reading pseudocode or a different language and my attention slips for a second:
foo = "this is a string"
# ...
foo.replace(" ", "_")
# ...
print(foo)
That call to str.replace is perfectly valid code. And unless I’ve misconfigured something, neither pylint nor Pylance report that there’s anything wrong with it.
But I can remember a couple of times where the call to replace was one small step of a larger string-processing function and there weren’t any other calls to replace nearby, so the call looked correct at first glance and didn’t stand out as “not like the others”; as a result, the bug took an embarrassing amount of time to find.
I don’t have the technical chops to boldly assert that the no_discard decorator is the best, perfect solution to this, but I can at least say that some way to flag these as “you probably goofed this” would save real time for real people.