Implicit conversion to boolean in if and while statements

A

Andrew Berg

This has probably been discussed before, but why is there an implicit
conversion to a boolean in if and while statements?

if not None:
print('hi')
prints 'hi' since bool(None) is False.

If this was discussed in a PEP, I would like a link to it. There are so
many PEPs, and I wouldn't know which ones to look through.

Converting 0 and 1 to False and True seems reasonable, but I don't see
the point in converting other arbitrary values.
 
S

Steven D'Aprano

This has probably been discussed before,

By the hoary hosts of Hoggoth, has it ever!
but why is there an implicit
conversion to a boolean in if and while statements?

It's nothing to do with if and while. All Python objects are duck-typed
as bools.

1) It's generally part of the duck-typing philosophy. If an object quacks
like a bool, why not treat it as a bool?

2) It's useful and convenient for short-circuit boolean expressions such
as any(), all(), and various things like:

for x in mylist or []:
...

is better than:

if mylist is not None:
for x in mylist:
...

3) Rather than distinguishing "true" from "false", a more useful
dichotomy is between "something" and "nothing". Python includes a number
of ways of spelling "nothing" of various types, such as:

None, 0, 0.0, '', [], {}, set()

and nearly everything else is "something".

4) Other languages such as Ruby, Javascript, PHP, Clojure and others also
distinguish between true-like and false-like ("truthy" and "falsey")
values. Although some of them have made some pretty weird and arbitrary
choices for what counts as true-like and false-like, without Python's
general principle that "nothing" values should be false.

(E.g. Javascript considers Boolean(false) to be a true value!!!)

5) Prior to Python 2.2, there was no bool type and no True and False
values. In fact, here is an impassioned plea from an educator begging
Guido not to introduce True and False to the language, because duck-typed
truthy/falsey values are *so much better*.

http://groups.google.com/group/comp.lang.python/msg/2de5e1c8384c0360?hl=en

Sadly, or happily, Python did grow True and False values, but the
fundamental distinction between something and nothing still exists.

(For the record, I can only think of one trap for the unwary: time
objects are false at *exactly* midnight.)
 
I

Ian Kelly

(For the record, I can only think of one trap for the unwary: time
objects are false at *exactly* midnight.)

Ugh, that's irritating. I can't think of any scenario where I would
ever want the semantics "if timeval (is not midnight):". This
reinforces the point that if you only want to test whether you have
None, you should use "is not None" rather than relying on __bool__.
 
R

Rick Johnson

Ugh, that's irritating. I can't think of any scenario where I would
ever want the semantics "if timeval (is not midnight):". This
reinforces the point that if you only want to test whether you have
None, you should use "is not None" rather than relying on __bool__.

I think this issue is not so much a "bool test" vs "type test", but more anambiguous syntax issue. Consider this:

## EXAMPLE A ##
py> if money:
.... do_something()

The syntax "if money" implies we are testing/measuring some attribute of "money", but what exactly about money are we testing/measuring? The problem lies in the syntax. To understand this syntax, we must first interpret what *IF* means, and we should *NEVER* need to interpret such a well defined word as *IF*! This syntax is far too opaque. Consider the alternative:

## EXAMPLE B ##
py> if bool(money):
.... do_something()

Now we have a hint. Even if we don't understand the inner workings of the "bool" function, we *do* understand that the statement "bool(money)" *must* return True or the block *will not* execute.

We must NEVER present "if" in such confusing manner as ExampleA. I believe Guido made a grave mistake allowing this syntax to flourish. His intentionswhere noble, to save people a few keystrokes, but all he accomplished was to pave a road directly into hell.

"Explict is better than Implict"
 
R

Rick Johnson

Ugh, that's irritating. I can't think of any scenario where I would
ever want the semantics "if timeval (is not midnight):". This
reinforces the point that if you only want to test whether you have
None, you should use "is not None" rather than relying on __bool__.

I think this issue is not so much a "bool test" vs "type test", but more anambiguous syntax issue. Consider this:

## EXAMPLE A ##
py> if money:
.... do_something()

The syntax "if money" implies we are testing/measuring some attribute of "money", but what exactly about money are we testing/measuring? The problem lies in the syntax. To understand this syntax, we must first interpret what *IF* means, and we should *NEVER* need to interpret such a well defined word as *IF*! This syntax is far too opaque. Consider the alternative:

## EXAMPLE B ##
py> if bool(money):
.... do_something()

Now we have a hint. Even if we don't understand the inner workings of the "bool" function, we *do* understand that the statement "bool(money)" *must* return True or the block *will not* execute.

We must NEVER present "if" in such confusing manner as ExampleA. I believe Guido made a grave mistake allowing this syntax to flourish. His intentionswhere noble, to save people a few keystrokes, but all he accomplished was to pave a road directly into hell.

"Explict is better than Implict"
 
A

Andrew Berg

3) Rather than distinguishing "true" from "false", a more useful
dichotomy is between "something" and "nothing". Python includes a number
of ways of spelling "nothing" of various types, such as:

None, 0, 0.0, '', [], {}, set()

and nearly everything else is "something".
Okay, I see the value in this, but I don't understand why None has a
truth value. I would expect None to mean "doesn't exist" or "unknown" or
something like that - e.g., a value of 0 means 0 jelly beans in the jar
and None means there isn't a jar.

FWIW, I have, for a reason I forget, gotten into the habit of writing
"if x is not None" when testing for None. However, I have not been
writing "if x is True: ..."/"elif x is False: ..."/"else: 'ruh-roh'"
when testing for True (in cases where a value of True or False makes
sense, but any other value would not). Should I?
 
A

Andrew Berg

Ugh, that's irritating. I can't think of any scenario where I would
ever want the semantics "if timeval (is not midnight):".
It's not implemented with such a test, but
logging.handlers.TimedRotatingFileHandler has an option to rollover at
midnight.
 
I

Ian Kelly

It's not implemented with such a test, but
logging.handlers.TimedRotatingFileHandler has an option to rollover at
midnight.

Nor could it be implemented with such a test, since the rollover check
would then have to run at exactly midnight for the test to evaluate
false. If it were off by 1 microsecond, it would miss it.
 
I

Ian Kelly

I think this issue is not so much a "bool test" vs "type test", but more an ambiguous syntax issue. Consider this:

## EXAMPLE A ##
py> if money:
... do_something()

The syntax "if money" implies we are testing/measuring some attribute of "money", but what exactly about money are we testing/measuring? The problemlies in the syntax. To understand this syntax, we must first interpret what *IF* means, and we should *NEVER* need to interpret such a well defined word as *IF*! This syntax is far too opaque. Consider the alternative:

## EXAMPLE B ##
py> if bool(money):
... do_something()

Now we have a hint. Even if we don't understand the inner workings of the"bool" function, we *do* understand that the statement "bool(money)" *must* return True or the block *will not* execute.

So now instead of having to understand how "if" handles arbitrary
values, we have to understand how "bool" handles arbitrary values.
How is that an improvement?

What should "if" do if presented a value that isn't True or False?
Raise a TypeError?

Next thing we know, people get so used to wrapping everything they
present to "if" in a "bool()" call, that they start writing silly
things like "if bool(x == 7)" and "if bool(isinstance(x, int))". Why?
Because it's faster and easier to automatically wrap the value in
"bool" than it is to put in the effort to verify that the value will
always be a bool to begin with in order to avoid a useless and
annoying exception. At the point that happens, the "bool()" is
effectively just part of the if syntax, and we're back to where we
started.
 
R

Rick Johnson

So now instead of having to understand how "if" handles arbitrary
values, we have to understand how "bool" handles arbitrary values.
How is that an improvement?

Because we are keeping the condition consistent. We are not relying on implicit resolution of an object's value based on some dark, esoteric and inconsistent rules that defy all normal logic.
What should "if" do if presented a value that isn't True or False?
Raise a TypeError?

YES! Because IT IS the author's responsibility to present a condition that evaluates to either True or False. Anything else would be ridiculously inconsistent.
Next thing we know, people get so used to wrapping everything they
present to "if" in a "bool()" call, that they start writing silly
things like "if bool(x == 7)" and "if bool(isinstance(x, int))".

We cannot prevent morons from doing stupid things. "x==7" IS an explicit statement that evaluates to either True or False. Likewise, isinstance(obj, type) is a function that evaluates to either True or False. Wrapping either example in a bool call is redundant and only obfuscates the meaning. True equals True and False equal False. Why do you need to test that truth?

The only time you will be forced to use the bool is when you are NOT using rich comparisons or NOT using truth testing functions in a condition. The following require NO bool function:

obj == obj -> bool
obj != obj -> bool
obj > obj -> bool
obj < obj -> bool
obj >= obj -> bool
obj <= obj -> bool
isinstance(obj, type) -> bool
callable(obj) -> bool
hasattr(obj, name) -> bool
issubclass(obj, name) -> bool
...along with any function that returns a bool

Whereas:
"if obj" -> Some esoteric semantics that defies all logic!
Why?
Because it's faster and easier to automatically wrap the value in
"bool" than it is to put in the effort to verify that the value will
always be a bool to begin with in order to avoid a useless and
annoying exception.

No, because we want our code to be EXPLICIT and consistent! Remember, writing obfuscated code is easy, however, interpreting obfuscated code is difficult! A good measure of your programming skill is to see how easily your code is read by the majority.

"""If it's difficult to explain, it's probably a bad idea""".

"if blah" is difficult to explain, whereas "if bool(blah)" is not.
At the point that happens, the "bool()" is
effectively just part of the if syntax, and we're back to where we
started.

That's a ridiculous conclusion. See points above^^^
 
R

Rick Johnson

So now instead of having to understand how "if" handles arbitrary
values, we have to understand how "bool" handles arbitrary values.
How is that an improvement?

Because we are keeping the condition consistent. We are not relying on implicit resolution of an object's value based on some dark, esoteric and inconsistent rules that defy all normal logic.
What should "if" do if presented a value that isn't True or False?
Raise a TypeError?

YES! Because IT IS the author's responsibility to present a condition that evaluates to either True or False. Anything else would be ridiculously inconsistent.
Next thing we know, people get so used to wrapping everything they
present to "if" in a "bool()" call, that they start writing silly
things like "if bool(x == 7)" and "if bool(isinstance(x, int))".

We cannot prevent morons from doing stupid things. "x==7" IS an explicit statement that evaluates to either True or False. Likewise, isinstance(obj, type) is a function that evaluates to either True or False. Wrapping either example in a bool call is redundant and only obfuscates the meaning. True equals True and False equal False. Why do you need to test that truth?

The only time you will be forced to use the bool is when you are NOT using rich comparisons or NOT using truth testing functions in a condition. The following require NO bool function:

obj == obj -> bool
obj != obj -> bool
obj > obj -> bool
obj < obj -> bool
obj >= obj -> bool
obj <= obj -> bool
isinstance(obj, type) -> bool
callable(obj) -> bool
hasattr(obj, name) -> bool
issubclass(obj, name) -> bool
...along with any function that returns a bool

Whereas:
"if obj" -> Some esoteric semantics that defies all logic!
Why?
Because it's faster and easier to automatically wrap the value in
"bool" than it is to put in the effort to verify that the value will
always be a bool to begin with in order to avoid a useless and
annoying exception.

No, because we want our code to be EXPLICIT and consistent! Remember, writing obfuscated code is easy, however, interpreting obfuscated code is difficult! A good measure of your programming skill is to see how easily your code is read by the majority.

"""If it's difficult to explain, it's probably a bad idea""".

"if blah" is difficult to explain, whereas "if bool(blah)" is not.
At the point that happens, the "bool()" is
effectively just part of the if syntax, and we're back to where we
started.

That's a ridiculous conclusion. See points above^^^
 
T

Terry Reedy

Ugh, that's irritating. I can't think of any scenario where I would
ever want the semantics "if timeval (is not midnight):". This

When printing time tables, midnight may be either 24:00 or 0:00,
depending on whether it is the end or start of a journey. That could, of
course, be done by explicit if time == midnight: rather than if not time:.

Whether to change the current behavior was discussed on python-ideas a
couple of months ago. I believe inertia and back-compatibity and the
rare use case won.
reinforces the point that if you only want to test whether you have
None, you should use "is not None" rather than relying on __bool__.

Right.
 
T

Terry Reedy

3) Rather than distinguishing "true" from "false", a more useful
dichotomy is between "something" and "nothing". Python includes a number
of ways of spelling "nothing" of various types, such as:

None, 0, 0.0, '', [], {}, set()

and nearly everything else is "something".
Okay, I see the value in this, but I don't understand why None has a
truth value.

Because everything does (or should).
I would expect None to mean "doesn't exist" or "unknown" or
something like that - e.g., a value of 0 means 0 jelly beans in the jar
and None means there isn't a jar.

FWIW, I have, for a reason I forget, gotten into the habit of writing
"if x is not None" when testing for None.

If x might possibly be any other false value (as is, I think, the usual
case), that is the right thing to do. Even if no other false value is
possible, I would still use 'is not None' just to be clear what the
false alternative is, and to guard against mistakes (like not knowing
that time values can be false) or code changes that add the possibility
of ohter false values.
However, I have not been
writing "if x is True: ..."/"elif x is False: ..."/"else: 'ruh-roh'"
when testing for True (in cases where a value of True or False makes
sense, but any other value would not). Should I?

If you only want to execute the if branch when x is literally the bool
object True and x could be some other non-bool true value such as 1 or
'a' or [1], etc, then you should.

If x is guaranteed to be True or False, which is the case you more or
less proposed, then you should not. For instance, "if isinstance(x, int)
is True:" or "if (n > 3) is True:" are redundant.
 
C

Chris Angelico

Because we are keeping the condition consistent. We are not relying on implicit resolution of an object's value based on some dark, esoteric and inconsistent rules that defy all normal logic.


YES! Because IT IS the author's responsibility to present a condition that evaluates to either True or False. Anything else would be ridiculously inconsistent.

Then the construct "if bool(some_condition):" is redundant. What
you've described is a viable system (REXX, for instance, demands that
an IF statement be given strictly either a 1 or a 0 (there's no True
and False values, but you're not allowed to use 527 as a True value,
it has to be 1)), but it's still redundant to attempt the cast. For
instance, this REXX function will logically negate a value twice, thus
forcing it to boolean:

bool: procedure
return \\arg(1)

But if it's given a non-bool, it'll just bomb, exactly the same as the
if statement would. So Ian's point still stands.

ChrisA
 
R

Ranting Rick

Then the construct "if bool(some_condition):" is redundant.

Wrong again, pay attention Chris!

It's ONLY redundant IF "some_condition" is a rich comparison: like
"(a==b)" OR a boolean function: like "callable(a)".

If HOWEVER we want to "truth test" an object (as in: "if obj") we
should be FORCED to use the bool! Why? Because explicit is better than
implicit and readability counts if we want to create maintainable code
bases!

if bool(obj) and a==b: # Correct!
if obj and a==b: # Incorrect!

Both lines of code currently produce the same result because
"somebody" decided to give objects esoteric boolean values. Sure, we
saved a few key stokes in a condition, but sadly at the cost of
readability and consistency. I see no reason why choosing implicit
resolution is better than explicit resolution. Saving six keystrokes
is simply not enough!

Python's motto has always been "readability counts", and for that
reason, we should return to Explicit Boolean Resolution if we want to
adhere to those principals.
 
C

Chris Angelico

If HOWEVER we want to "truth test" an object (as in: "if obj") we
should be FORCED to use the bool! Why? Because explicit is better than
implicit and readability counts if we want to create maintainable code
bases!

if bool(obj) and a==b: # Correct!
if obj and a==b: # Incorrect!

That still doesn't answer the question of what bool(obj) should do if
obj is not a bool, and why if can't do the exact same thing, since if,
by definition, is looking for a boolean state selector.

ChrisA
 
S

Steven D'Aprano

Ugh, that's irritating. I can't think of any scenario where I would
ever want the semantics "if timeval (is not midnight):".

Yes, it is a genuine gotcha. Time values are numbers, and zero is falsey,
so midnight is falsey even though it shouldn't be.

There's no good solution here, since we have a conflict between treating
time values as time values ("midnight is nothing special") and as numbers
("midnight == 0 which is falsey"). The only "solution" is to throw out
duck-typing of boolean values, which is tossing the baby out with the
bathwater -- bool duck-typing works fine for everything else.

This
reinforces the point that if you only want to test whether you have
None, you should use "is not None" rather than relying on __bool__.

Often you should, but I didn't mention anything about testing for None.
Testing objects in a boolean context is more than just testing for None.

I have just written a bunch of code with about two dozen examples similar
to this:

for item in (seq or []):
do_something_with(item)

iterates over seq if it is non-empty, or the empty list. Writing it like
this would be more painful, more complex, less readable and less
idiomatic:

if seq is not None:
for item in seq:
do_something_with(item)


not to mention completely unnecessary if you have already checked that
seq is either None or a sequence, and not some other arbitrary value.

(If seq really could be any type at all, then an explicit identity test
against None is required.)


One of my favourites:

value = (dict or {}).get('key')

instead of:

value = None if dict is None else dict.get('key')
 
D

Devin Jeanpierre

That still doesn't answer the question of what bool(obj) should do if
obj is not a bool, and why if can't do the exact same thing, since if,
by definition, is looking for a boolean state selector.

If can obviously do the exact same thing -- it does, in Python.

I don't agree with the angle that Rick is spinning, so let me write my
own: By forcing the objects in conditional to be booleans, you are
forced to do something to non-booleans to convert them. By doing so,
you will help inform the reader what the non-boolean is, which makes
it easier for them to figure out the code.

For example, instead of "if stack:" or "if bool(stack):", we could use
"if stack.isempty():". This line tells us explicitly that stack is a
container. Or instead of "if dist:" or "if bool(dist):" we could use
"if dist == 0:". This tells us explicitly that stack is a number.
Supposedly this makes it easier to read code. It certainly reads more
like English! :)

As far as I know, the only use of having a polymorphic boolean
conversion is reducing the amount of typing we do. Generally objects
with otherwise different interfaces are not interchangeable just
because they can be converted to booleans, so you wouldn't lose much
by being forced to explicitly convert to boolean with something
interface-specific.

-- Devin
 
R

Ranting Rick

That still doesn't answer the question of what bool(obj) should do if
obj is not a bool, and why if can't do the exact same thing, since if,
by definition, is looking for a boolean state selector.

ChrisA

My point is no different than this example:

py> cost = 1.75
py> cost
1.75
py> 'Cost = ' + cost

Traceback (most recent call last):
File "<pyshell#17>", line 1, in <module>
'Cost = ' + cost
TypeError: cannot concatenate 'str' and 'float' objects
py> 'Cost = ' + str(cost)
'Cost = 1.75'

We DON'T want Python to silently convert "cost" to a string. What we
DO want is to force the author to use the str function thereby making
the conversion explicit.

Same with converting objects to bools.

We DON'T want "if" to magically convert a non-boolean into a boolean.
What we DO want is to force the author to use the bool function
thereby making the conversion explicit. By doing so we transform
confusion into comprehension. By doing so we maintain the principals
of readability counts.
 
S

Steven D'Aprano

3) Rather than distinguishing "true" from "false", a more useful
dichotomy is between "something" and "nothing". Python includes a
number of ways of spelling "nothing" of various types, such as:

None, 0, 0.0, '', [], {}, set()

and nearly everything else is "something".
Okay, I see the value in this, but I don't understand why None has a
truth value.

And this is exactly the sort of mental confusion that Laura Crichton
warned about (see the link I included earlier).

Thinking about "truth values" is harmful, since that's arbitrary. That
way goes to Javascript, PHP, Ruby etc. that seem to arbitrary pick
whatever things are truthy or falsey according to some random whim, or
according to some implementation detail that is meaningless outside of
the implementation, such as Javascript insisting that while false is
falsey, if you box it in an object it becomes truthy.

It's crap like that which gives duck-typing bools a bad name.

The distinction you should consider is:

- is it something, or nothing?

(relative to the type in question, of course)

Python (at least the built-ins, third-party code can do any old crap they
want) is consistent in this. Instances which represent something/non-
emptiness are true, those which represent nothing/emptiness are false.

0? That's the int that represents nothing, so it's false.

23.723? That's one of many floats that represents something, so it's true.

'spam'? That's one of many non-empty strings, so it's true.

''? That's an empty string, that it, it contains nothing, so it is false.

None? That represents a lack of a thing, that is, nothing, so it's false.

(Please don't get into a great philosophical debate over whether
nothingness is itself something. That impressed me when I was 15. But now
I know about reification: just because we have a name for a concept
doesn't mean that the concept is something concrete. None is an object,
but it *represents* the lack of an object.)

I would expect None to mean "doesn't exist" or "unknown" or
something like that - e.g., a value of 0 means 0 jelly beans in the jar
and None means there isn't a jar.

How you interpret some_variable = None depends on what some_variable
represents. If some_variable represents "number of jelly beans in a jar",
then that should be 0 if there is no jar.

If you want to create a language with ternary truth values (yes, no, mu)
or some larger number (yes, no, maybe, mu, contradiction, unknowable,
....) be my guest. Just do everyone a favour and do some research on the
large literature on non-boolean logic systems first.

FWIW, I have, for a reason I forget, gotten into the habit of writing
"if x is not None" when testing for None. However, I have not been
writing "if x is True: ..."/"elif x is False: ..."/"else: 'ruh-roh'"
when testing for True (in cases where a value of True or False makes
sense, but any other value would not). Should I?

Only if you want people to laugh at you.

If you *genuinely* want to implement Java in Python, then be explicit
about your type-testing:

if isinstance(x, bool) and x: ...

or even

if type(x) is bool and x: ... # disallow subclasses

Otherwise, where do you stop?

if x is True is True is True is True is True is ...


Or you could just write idiomatic Python code, including duck-typing, and
that includes duck-typing bools. Why do you care if somebody calls your
function with flag=1 instead of flag=True?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,743
Messages
2,569,478
Members
44,898
Latest member
BlairH7607

Latest Threads

Top