Implicit conversion to boolean in if and while statements

R

Ranting Rick

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.

Short circuitry is a powerful tool! But why the heck would your
sequences ever be None? Are you using None as a default? And if so,
why not use an empty sequence instead ([], {}, "")?
 
R

Ranting Rick

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! :)

Yes, but this approach involves adding new "value testing" methods to
every object.

Whilst these specific methods would probably inject more comprehension
than using bool, i believe the bool function can handle this problem
better due to its monolithic and generic nature. No need to memorize
which method is needed for strings, or integers, or lists, etc... just
use bool and everything works. As for the semantics, we should let the
object decide how to respond to a __bool__() request.

But what's the point of having a bool function if we refuse to use it
correctly? We force str, int, and float conversion all day, but not
the bool? Where is the consistency? Where is the bool!?
 
C

Chris Angelico

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)

Short circuitry is a powerful tool! But why the heck would your
sequences ever be None? Are you using None as a default? And if so,
why not use an empty sequence instead ([], {}, "")?

Function default arguments spring to mind, especially if the list will
be mutated afterwards.

ChrisA
 
S

Steven D'Aprano

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 this is why Rick always writes code like:

integer_value_three = int(1) + int(2)
assert (int(integer_value_three) == \
int(3) is True) is True, str("arithmetic failed")
list_containing_three_values_which_are_all_integers_but_might_later_have_more_or_fewer_values_or_other_types = list([1, 2, integer_value_three])

because you can never have too much explicitness. Who wouldn't want
to read code like that?

and readability counts if we want to create maintainable code bases!

Yes you, Rick, are correct, that is to say not wrong, that readability,
that is to say the quality of ease of reading the text in question,
counts, that is to say that it matters to people who care about ease of
reading, when our motivation is to create, that is to say write,
maintainable code bases, that is to say unified collections of code which
can have software errors fixed and new features added with relatively
small amounts of effort on behalf of the human programmer.

And that, the reason given in the sentence above, is the reason that we,
collectively all programmers, should prefer to be explicit, not merely
conveying meaning by implication about everything we, collectively all
programmers, write, including typing, use of speech-recognition software,
or any future technological process by which text or program code or both
is transcribed from the idea of the human person to a permanent form
recorded where other people, or non-human sentient beings, can read or
otherwise gain access to it for the purpose of understanding the content
of the test or program code or both.
 
C

Chris Angelico

And that, the reason given in the sentence above, is the reason that we,
collectively all programmers, should prefer to be explicit, not merely
conveying meaning by implication about everything we, collectively all
programmers, write, including typing, use of speech-recognition software,
or any future technological process by which text or program code or both
is transcribed from the idea of the human person to a permanent form
recorded where other people, or non-human sentient beings, can read or
otherwise gain access to it for the purpose of understanding the content
of the test or program code or both.

I'd rather be booled in oil.

ChrisA
*ducks for cover*
 
R

Ranting Rick

And this is why Rick always writes code like:
....

Traceback (most recent quip last):
Author: "<DeAprano>", line 7, in <post>
LogicalFallacyError: "Reductio ad absurdum"
 
S

Steven D'Aprano

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.

isempty is not a container method.

py> container = []
py> container.isempty()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute 'isempty'

Your code tells us explicitly that stack is expected to be an object with
an isempty() method. What does that mean? Who knows?

calories = macdonalds.fries('large')
calories.isempty()
=> returns True



When you want to write polymorphic code to handle your stack, you end up
doing something like this:

if isinstance(stack, MyStackClass):
flag = stack.isempty()
else:
try:
# list, collections.deque, many others
flag = len(stack) == 0
except AttributeError:
try:
if sys.version < '3':
flag = not stack.__nonzero__()
else:
flag = not stack.__bool__()
except AttributeError:
# Is this even possible in Python 3?
flag = False # I guess...
# If we get here, flag is true if stack is empty.
if flag:
...

Yeah, explicit is *so much better* for readability. Can't you just *feel*
how much more readable all those irrelevant implementation details are?

If you're smart, you wrap all of the above in a function:

def isempty(stack):
# blah blah as above


But if you're *really* smart, you write to the interface and let Python
take care of the polymorphic details for you:

if not stack:
...


(Assuming that stack defines __nonzero__ or __len__ correctly, which it
better if it claims to be a container.)

It boggles my mind that people who are perfectly happy to program to an
interface or protocol when it comes to (say) iterables, numbers or even
big complex classes with dozens of methods, suddenly freak out at the
thought that you can say "if obj" and obj is duck-typed.

There's a distinct lack of concrete, actual problems from duck-typing
bools, and a heavy over-abundance of strongly-held opinion that such a
thing is self-evidently wrong.

As far as I know, the only use of having a polymorphic boolean
conversion is reducing the amount of typing we do.

The same could be said about *every* polymorphic function.

The benefit is not just because you don't wear out your keyboard as fast.
The benefit is the same for all other polymorphic code: it lets you write
better code faster with fewer bugs and less need for unnecessary type
restrictions.

If there are a few corner cases where you actually *need* to restrict the
type of your flags to a actual bool, well, Python gives you the tools to
do so. Just as you can restrict the type of a sequence to exactly a list
and nothing else, or a number as exactly a float and nothing else. Just
do your type tests before you start operating on the object, and reject
anything that doesn't match what you want.

But that should be the exception, not the rule.

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.

Until somebody writes an awesomely fast stack class in C and gives it an
is_empty() method instead of isempty, and your code can't use it because
you made unnecessary assumptions about the implementation.
 
S

Steven D'Aprano

...

Traceback (most recent quip last):
Author: "<DeAprano>", line 7, in <post>
LogicalFallacyError: "Reductio ad absurdum"


Deary deary me Rick. Reductio ad adsurdum is not a fallacy. It is a
counter-argument to an argument or claim, by showing that the premise of
the original claim leads to an absurd conclusion.

You have claimed that we should always be explicit whenever we write. But
you do not actually live up to your own advice, because you can't: it is
absurd to try to be explicit about everything all the time. You have
misunderstood the purpose of the Zen of Python: it is not to claim that
everything should be explicit, but to avoid code that is hard to
understand because things which need to be explicit for clarity are
implied by other parts of your code.

(It's not like explicit and implicit are distinct -- everything depends
on something implicit, if only the meaning of the words you use to
describe it.)

It certainly doesn't mean that the semantics of Python the language must
be written out explicitly every time you use each feature.

for x in sequence: # Yes, implies that we iterate over the values

for loop variable named x in iterable sequence iterate over values and
assign the loop variable each time you go around the loop executing the
following block each time:
# No, since that tells us what we already know and just adds
# meaningless verbosity for the sake of faux "explicitness"
 
R

Ranting Rick

It boggles my mind that people who are perfectly happy to program to an
interface or protocol when it comes to (say) iterables, numbers or even
big complex classes with dozens of methods, suddenly freak out at the
thought that you can say "if obj" and obj is duck-typed.

"if obj" is in essence doing "if bool(obj)" behind the scenes. My
question is: Why hide such valuable information from the reader? It's
obvious that "if bool(obj)" will return a boolean; whereas "if obj" is
ambiguous.
There's a distinct lack of concrete, actual problems from duck-typing
bools, and a heavy over-abundance of strongly-held opinion that such a
thing is self-evidently wrong.

If the multitudes of misunderstandings from "if obj" on this list have
not convinced you yet, then i lack the energy to educate you!
The same could be said about *every* polymorphic function.

For which "bool" IS!

Wikipedia to the rescue:
"""In computer science, polymorphism is a programming language feature
that allows values of different data types to be handled using a
uniform interface. The concept of parametric polymorphism applies to
both data types and functions. A function that can evaluate to or be
applied to values of different types is known as a polymorphic
function."""

bool("a") -> True
bool(0) -> False
bool([1,2,3]) -> True
bool(True) -> True
The benefit is not just because you don't wear out your keyboard as fast.
The benefit is the same for all other polymorphic code: it lets you write
better code faster with fewer bugs and less need for unnecessary type
restrictions.

There are NO type restrictions for bool.
If there are a few corner cases where you actually *need* to restrict the
type of your flags to a actual bool, well, Python gives you the tools to
do so.

Yes, the bool()
 
R

Ranting Rick

(It's not like explicit and implicit are distinct -- everything depends
on something implicit, if only the meaning of the words you use to
describe it.)

It certainly doesn't mean that the semantics of Python the language must
be written out explicitly every time you use each feature.

Of course not. Don't be ridiculous.
for x in sequence: [...]

This syntax is explicit *enough*. We don't need to be any more
explicit.

But if you are going to argue that "if obj" is *explicit enough*, then
apply your argument consistently to "String"+1.75 also. Why must we be
explicit about string conversion BUT not boolean conversion? Can you
reduce this to the absurd? Or will you just choose to ignore this
valid point?
 
C

Chris Angelico

"if obj" is in essence doing "if bool(obj)" behind the scenes. My
question is: Why hide such valuable information from the reader? It's
obvious that "if bool(obj)" will return a boolean; whereas "if obj" is
ambiguous.

Proves nothing. At least when there are less names used, there's less
possibility of monkey-patching.
.... return 5
....
.... print("Yay?")
....
Yay?

ChrisA
 
C

Chris Angelico

But if you are going to argue that "if obj" is *explicit enough*, then
apply your argument consistently to "String"+1.75 also. Why must we be
explicit about string conversion BUT not boolean conversion? Can you
reduce this to the absurd? Or will you just choose to ignore this
valid point?

Personally, I'm quite okay with automatic upcasts to string. But if
you want to be explicit, particularly with floats, the solution often
is not to use str(), but a proper number-formatting routine. You want
"String%f"%1.75 for full power.

But when you're just letting the language do the translation, it's
much of a muchness whether you put an explicit toString() call in.

ChrisA
 
M

Mark Lawrence

I'd rather be booled in oil.

ChrisA
*ducks for cover*

What a silly bunt[1] :)
*also ducks for cover*

[1] from a Monty Python sketch for those who don't know about a guy who
pronounces c's as b's, hence Kings Bollege Bambridge
 
A

alex23

"if obj" is in essence doing "if bool(obj)" behind the scenes. My
question is: Why hide such valuable information from the reader?

If @decorator is in essence doing "function = decorator(function)"
behind the scenes, why hide that?
If classes are just syntactic sugar for dicts of functions, why hide
that?
It's
obvious that "if bool(obj)" will return a boolean; whereas "if obj" is
ambiguous.

It's only ambiguous if you're the sort of idiot who believes every
language should conform to their a priori expectations. The behaviour
is _documented and standard_, so it's in no way ambiguous unless
someone has actively failed to do _their_ part and _educate
themselves_:

http://docs.python.org/library/stdtypes.html#truth-value-testing
If the multitudes of misunderstandings from "if obj" on this list have
not convinced you yet

By that argument, _every_ Python feature that is misunderstood should
be modified to meet the expectations of those who have refused to
understand the language. In which case, why even bother having
different languages with different ways of doing...oh wait, I'm
talking to the monoculture king. Forget that line of reasoning.

Also: citation or STFU. Provide links to 3 threads from the last month
that have revolved around misunderstanding of truth values.
For which "bool" IS!

Yes. That was Steven's point, which you missed in all of your usual
mouth frothing.
 
A

alex23

But if you are going to argue that "if obj" is *explicit enough*, then
apply your argument consistently to "String"+1.75 also. Why must we be
explicit about string conversion BUT not boolean conversion?

What _other_ than booleans can you expect a condition to reduce down
to? Seriously. What might you possibly expect 'obj' in 'if obj' to be?
Tuesday? The colour mauve? That sinking feeling that you're entering
into a debate that's far above your ability to understand it?

Now: as an expression "String"+1.75 can be _anywhere_, so _what_ you
want will very much be contextual. Do you want "String1.75"? Do you
want float("String") + 1.75? Do you want it to error? So yes, here you
very much need to be explicit.
Can you reduce this to the absurd?

You've already taken care of that.
 
S

Steven D'Aprano

But if you are going to argue that "if obj" is *explicit enough*, then
apply your argument consistently to "String"+1.75 also. Why must we be
explicit about string conversion BUT not boolean conversion?

The problem with "String" + 1.75 is not lack of explicitness, but
ambiguity. The + is operator is plenty explicit, but it is ambiguous when
the operands have different types. Should it...?

- truncate "String" at the first non-digit (hence "") and then coerce
it to 0.0, and hence return the float 1.75?

- coerce "String" to a float NaN on the basis that "String" is
not a number, and hence return NaN?

- coerce 1.75 to a string, and hence return "String1.75"?


The first behaviour is rather dubious, but a good case could be made for
the second or third. Python takes a fourth approach, and refuses to allow
such mixed type operations.

If + always meant "numeric addition", and & was used for string
concatenation, then we could have unambiguous expressions using mixed
types:

1 + 1.75 # int + float always coerces to float
1 + "2" # int + str always coerces to int
1 & "2" # int & str always coerces to str

but since & is used for integer bitwise-and, and + is used for both
concatenation and addition, we can't, and so Python raises an exception.

For arithmetic, there is an unambiguous hierarchy of types, the numeric
tower, which tells us which types coerce to what, e.g.:

int -> float -> complex

But there is no such hierarchy when it comes to (say) mixed strings and
lists, etc., and hence Python raises an exception rather than guessing
which type you wanted as the result.

This is completely irrelevant when it comes to bools -- we don't have to
coerce a value into another type, we just need to know if it is something
or nothing. The object itself is best able to make that decision, hence
delegating it to a protocol and method:

- If the object is a container, and it has a length of 0, it is empty
and hence nothing (falsey); if it has a non-zero length, it is non-empty
and hence something (truthy).

- Otherwise ask the container whether it is something or nothing by
calling __nonzero__ (the original name) or __bool__.


Python makes a rather big blunder, at least from the perspective of
consistency. Bools are ints:

py> issubclass(bool, int)
True
py> isinstance(True, int)
True
py> isinstance(False, int)
True

but there are things that can be converted into bools that can't be
converted into ints, even though bools *are* ints! Contradiction.

py> x = [None, 42, '']
py> bool(x)
True
py> int(x)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: int() argument must be a string or a number, not 'list'


Since x can be converted into True, and True == 1, you should be able to
convert x into 1. But that's crazy, since x = [None, 42, ''].

*shrug* I don't call this a gotcha, but it is one of the more ugly
consequences of Python's bool implementation.

Can you
reduce this to the absurd? Or will you just choose to ignore this valid
point?

Mu. (Neither true nor false.)
 
S

Steven D'Aprano

Short circuitry is a powerful tool! But why the heck would your
sequences ever be None? Are you using None as a default? And if so, why
not use an empty sequence instead ([], {}, "")?


Mostly for explicitness. I want to be able to say that there is
*explicitly* no seq, not merely that it happens to be a list which right
now is empty but later might not be. Using None for missing values is
idiomatic Python.

You should approve, if only for the sake of consistency: None is meant to
be used as "no such value", and that's exactly how I am using it, even at
the cost of my convenience when writing the code.
 
D

Dennis Lee Bieber

On Sun, 15 Jul 2012 12:02:37 -0500, Andrew Berg wrote:


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

Would one rather have the behavior seen in SQL for Null?
http://www.firebirdsql.org/file/documentation/reference_manuals/user_manuals/Firebird-Null-Guide.pdf

Hey, let's turn the IF statement into tri-state logic...

if cond:
#true branch
else:
#false branch
unknown:
#when cond includes a None term that was NOT explicitly
#part of a "... is {not} None" clause
 
A

Albert van der Horst

Ranting Rick said:
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.

We do want Python to silently convert "cost" to a string in the
proper context.

cost= 3.75
print( cost )
Same with converting objects to bools.

I think "if" is sufficient context to convert something to a boolean.
It now is a matter of good taste and what fits best in Python as a
whole. Nothing to be dogmatic about.

Groetjes Albert
 

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

No members online now.

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,048
Latest member
verona

Latest Threads

Top