Yeah I think this is a good point as it would circumvent the point of boolif. In some way I consider the type annotation free world of python to be something of the “Wild West” in terms of validating types. My concern is much more that currently (with the exception of mypy as demonstrated earlier, in this function case) - type checkers really don’t offer much help with this problem, because there’s no way to flag cases that are technically valid but not what was intended by the user (without an additional line).
This is what the “in line type checking” I described above intends to solve, although I think this probably does deviate too much from the original boolif idea. I will think on more examples of where this would be useful, and open a new discussion thread, as I think the boolif idea itself has many more issues.
I don’t understand your example here, but note that you can get mypy to complain even more about this kind of thing by running mypy --enable-error-code truthy-bool on your code. This lint has a high number of false positives, so it is disabled by default even in mypy’s --strict mode, but it sounds like you might find it helpful.
If mypy has this feature but other type checkers don’t, that just sounds like an argument for filing feature requests with the other type checkers (or, if this matters a lot to you, switching from your current type checker to mypy).
Oh please no. Excessive use of type hints are already bloating Python code, but at least they are (mostly) limited to annotations in function signatures and a few other places. Adding annotations to arbitrary parts of expressions would be a horrorshow.
I’ve made a separate post for the in-line type hints suggestion, if people want to check it out. I’m quite convinced by the issues people have raised with boolif, so I’m not sure it’s worth pursuing further, but I think this new solution covers many more cases, the boolif one included:
I’ve often thought that a “strict” if would have been a better choice, Being explicit can be helpful, and there’s nothing inherently “false” about a zero value. But that ship as LONG since sailed
But this example (function objects being Truthy) does bring up a point – is it really helpful that ALL object have a Truthyness? numbers,containers, etc make a lot of sense, but is is evey useful for a function to be truthy?
Anyway, antoher way to make your code a little less prone to these kinds of errors is to explicitly look for True:
if somethign is True:
...
That would have caught the example bug.
But as stated by others, that could break a lot of code that IS passing things around expecting “truthyness”, rather than an actual Bool value.
Also – this falls into what I’ve always thought most type errors are: shallow bugs – if this kind of error is not caught by tests, you don’t have very good test coverage – and when it does fail, it’s usually pretty easy to see what’s wrong and fix it.
Yes, because it allows you to have a variable that might contain a function, or might contain None, and you can use its truthiness to check for that. Extremely convenient and quite common.
Why? Truthiness is entirely guaranteed. What if I have a number that could be 0 and could be something else - am I supposed to test with == 0 rather than boolean conversion?
defends against introduction of non-function objects, including objects with __bool__ methods that have unintended consequences, and
reminds the reader that you’re filtering out None elements rather than burdening the reader with figuring out which implicitly false objects are being filtered.
Or the reader just understands that, if there’s a function, you call it. If there isn’t, you don’t.
if func: func(thing)
That code doesn’t need to be rewritten like this:
if func is not None: func(thing)
if callable(func): func(thing)
if (func is not None) is True: func(thing)
These are more explicit, but are they any more correct?
The standard interpretation of “Explicit is better than implicit” is that “Explicit” means “Code that I like” and “Implicit” means “Code that I don’t like”. Everyone’s idea of good code is different, and there is always a way to be “less implicit” in a way that makes your code worse.
At this point, the thread should probably be moved out of Ideas. I don’t think there’s anything here that is going to be an actual language idea.
None of those examples are like what I suggested since calling the object nearly always succeeds whenever the object is callable, and nearly always fails whenever it isn’t. Whereas, there are plenty of objects that are falsy that are not none, or that don’t compare equal to zero.
But how is that usefully different? If there’s a callable that happens to be falsy, it’ll be ignored, but that would be a VERY weird situation. OTOH, if something other than a function slips through where it’s expecting a function, I would expect a TypeError, not silently ignoring it. So the truthiness test should be fine; the is None check is also fine, but unnecessary; and checking if it’s callable is a declaration in code that it could be any object, but we want to call it only if it’s callable, which is very different in concept from “if there’s a function, call it”.
Yeah, but you’ll get the type error much later. If those callables are stored in a some callback list, they may not be called for ages.
And that was only my first point about defensive coding. It also makes things easier for readers to compare to 0 or 0.0 or checking identity with None.
Your argument is that after filtering the list of callables, you’ll call them and then you’ll get type errors if anything else sneaks in. But you may not call them immediately, so this isn’t always a good argument.
Are you saying that we should have explicit type conversions such that 0 is not equal to 0.0?
No. I’m saying that comparing with 0 or 0.0 or None:
When I was young and foolish and learning Pascal, I (like many beginners) got into the bad habit of writing bool tests like this:
var:
myflag: boolean;
if myflag = true then ...
(Please excuse any errors in that snippet, my Pascal is very rusty.)
But of course, since I didn’t trust the bool variable to be a bool, to be extra sure I should have written
if (myflag = true) = true then ...
or even
if ((myflag = true) = true) = true then ...
Its hard to know when to stop.
I won’t say that your advice to compare directly against True always falls into the same category. After all, maybe somebody needs five-valued boolean logic:
if flag:
if flag is True:
do_this()
else:
do_that()
else:
if flag is False:
do_spam()
elif flag is None:
do_eggs()
else:
do_cheese()
No idea where that notion of “filtering the list of callables” came from. I was talking about a pretty common pattern where you either have a function or don’t have a function, and the obvious thing to write is:
if func: func(thing)
So… it’s being called. Right here. If you give it something that is truthy but not a function, you get a TypeError. It’s pretty obvious what’s happening.
Where did func come from? Could be anywhere, but that’s a completely separate problem. (Quite likely it was a parameter to a function at some point. Who knows, who cares.) But right here, is it really necessary to test whether it is not None, or can we just check whether it exists? Either you have a function, or you don’t. That’s all you have to worry about.
I got to say, I don’think I’ve EVER doen that – it is a very common pattern for a variable to be “unset” by being set to None, and if you need to check for that you use:
if var is None:
do_something_with_var
In fact, I’ve seen bugs where folks use:
if var:
do_something_with_var
and end up with errors when var happens to be falsy – there is a big difference between “anything Falsy” and “None”.
I don’t see any reason to treat things that are supposed to be None or a callable differently, even though it is very unliley that a callable will be Falsy. Not impossible mind you – a callable class could very will use bool for some other useful purpose – like indicating emptyness.
And it’s similar for:
if var == 0.0:
There is nothing inherently “Falsy” about zero – it all depends on what that value means – if I want to know if it’s unset, I check for None, if I want to know if its value is zero, I should check for that, just like I would check for its value being 5, or greater or less than some value.
I do like, and use, Truthiness to check for an empty container for the most part – as in my mind that is inherently Falsy.
This seems to me analogous to rarely using bare except: when you write a try:except block you should know what kind(s) of Exceptions you are prepared to handle, and you don’t want any other Exceptions to leak though. In an “if” block you should know what kinds of Truthiness you are expecting, and you don’t want other Truthy objects to leak through.
Now that I"ve written all that – I agree, this is not a discussion that belongs in ideas … Is there a “Pythonic style” forum?