Implicit conversion to boolean in if and while statements

A

Albert van der Horst

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?

Java programmers?

(Couldn't resist ;-) )

Groetjes Albert
 
E

Ethan Furman

Steven said:
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").

--> import datetime
--> mn = datetime.time(0)
--> mn
datetime.time(0, 0)
--> mn == 0
False

Apparently, midnight does not equal zero. Possibly because it should be
truthy. ;)

~Ethan~
 
A

Andrew Berg

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.
What is None supposed to mean then, and what should I do when I have to
make a distinction between "doesn't exist" and "empty"? Sure, if I need
to count the total number of jelly beans in all my stores, the
distinction is meaningless, but if I need to find out which stores sell
jelly beans so I know which stores need to be restocked, the distinction
is quite important.
 
E

Ethan Furman

Andrew said:
What is None supposed to mean then, and what should I do when I have to
make a distinction between "doesn't exist" and "empty"? Sure, if I need
to count the total number of jelly beans in all my stores, the
distinction is meaningless, but if I need to find out which stores sell
jelly beans so I know which stores need to be restocked, the distinction
is quite important.

I'm not sure what Steven was trying to say there, but for me:

jar with no jellybeans == 0

no jar == None

~Ethan~
 
L

Laszlo Nagy

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.

I agree. There is no pont in abolutizing explicitness anyway. It is not
a yes/no question. You cannot tell that somebody is "not explicit". It
is not something that can be decided. But you can say that he was "not
explicit enough" in a concrete case. There is an accepted level of
explicitness. You can probably always be more expicit, or less explicit.
Being more explicit is not the goal. But is a good practice to be more
explicit if it helps you achieve the real goal. For example, writting a
program that can be maintained easily.
 
L

Laszlo Nagy

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?
Not all decisions in Python are based purely on the "explicit enough"
thing. Some things in the language cannot be explained using
explicitness alone. So, when we cannot fully explain the ' why
String+1.75 ' question with statements about explicitness then it
doesn't mean that anybody lied or wrote something wrong. :)
 
S

Steven D'Aprano

I'm not sure what Steven was trying to say there, but for me:

jar with no jellybeans == 0

no jar == None

The existence of a jar or no jar is irrelevant to the question of how
many jellybeans there are. They are two different things, and therefore
need two different values. There are many ways to implement this.

# One way
jar_exists = True # or possibly False
jellybeans = 42 # or whatever the number is, possibly 0

# Another way
jar = Jar(number_of_beans=jellybeans) if jar_exists else None
jellybeans = jar.jellybeans if jar is not None else 0

If you have many jars, and you want to count the total number of
jellybeans:

total = sum(jar.jellybeans for jar in jars if jar is not None)


Strictly speaking, the "is not None" is redundant, but it expresses the
intent better than the alternative. Assuming that jar instances follow
the standard Python API for containers, and is treated as falsey when it
has a jellybean count of zero:

total = sum(jar.jellybeans for jar in jars if jar)
# relies on the fact that adding zero to a number makes
# no difference, so you can safely leave zeroes out


Here's a case where you *must* distinguish between empty jars and None:

number_of_jars = sum(1 for jar in jars if jar is not None)

and a case where you *shouldn't*:

number_of_nonempty_jars = sum(1 for jar in jars if jar)

Of course you can write this:

number_of_nonempty_jars = sum(
1 for jar in jars if jar is not None and jar.jellybeans > 1
)

but that overwhelms the code with incidental implementation details about
jellybean counts, which is prone to bugs. (Did you spot it?)

Even jar.isempty() would be better, but better still is to *not* invent
your own container API but to use the standard Python one instead and
define an appropriate __nonzero__ (Python 2) or __bool__ (Python 3)
method.

If I insist on making a single object do duty for both the jar and the
jellybean count, then I need a "null jar object", and I probably end up
with something like this:

Jar(number_of_beans=None) => null jar object with jar.jellybeans = 0
Jar(number_of_beans=0) => normal jar object with jar.jellybeans = 0
Jar(number_of_beans=42) => normal jar object with jar.jellybeans = 42

and then my code becomes even more complicated and less understandable,
but hey, it's *my* code and I can do whatever damn-fool thing I like!
 
S

Steven D'Aprano

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


That's a 51 page document. I'm not sure I know which behaviour you are
referring to.

Seems to me that the Firebird NULL object is closer to a float NaN than
to Python's None, except that Firebird treats comparisons with NULL as
returning a NULL, while Python treats comparisons with NaN as True or
False.

Both behaviours are reasonable, but the Firebird behaviour seems to be
more error-prone.

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

I'm not sure if you're being sarcastic here or not. Ternary logic is
perfectly reasonable, although I expect that it would be error-prone
because programmers would forget the "unknown" clause all the time. It
looks like Firebird implements the variety of ternary logical called
"Keene logic".

Of course, ternary logic can always be re-written in binary terms.
Assuming that UNKNOWN evaluates as false:

if flag:
true-clause
else:
if flag is UNKNOWN:
unknown-clause
else:
false-clause
 
A

Andrew Berg

Because everything does (or should).
I can see how truth testing for empty values is convenient, but perhaps
objects should only have a truth value if explicitly given one -
particularly in cases where such a value wouldn't be obvious or the
obvious value isn't the actual one:
{}

Would it not be reasonable to expect an empty namespace to have a truth
value of False since [] and friends do? It's a bit of a gray area for an
object defined by "class C: pass", but this is *specifically intended*
to be a namespace. Why should things like functions or exceptions have
truth values?

Note: For those who aren't aware, types.SimpleNamespace was added in
3.3.0b1.

On a side note, I tend to do this:

x = some_list
try:
y = x.pop()
except IndexError:
do_something_else()

rather than:

if x:
y = x.pop()
else:
do_something_else()

but of course that only applies to empty lists/sets/related and not
0/0.0/None and only when I want something from the list/set/whatever.
 
R

rusi

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

If you know some English, its clear that if and while create bool
contexts.
[If you know English but have not studied logic the 'if/while' make
sense whereas 'bool' is gobbledygook]

The issue is not where the cast goes to -- this clearly is bool
But where the cast comes from -- which requires tracing program-paths
 
A

Andrew Berg

The existence of a jar or no jar is irrelevant to the question of how
many jellybeans there are. They are two different things, and therefore
need two different values. There are many ways to implement this.
I have a better real example, but I opted not to use it before since it
requires some explanation - IRC messages.
A client-to-server message has the basic form of b'COMMAND arguments
:message' (e.g. b'PRIVMSG #channel :hi guys!'). Some commands have no
message part because there is no message associated with it (e.g. b'JOIN
#channel') and there is at least one where there is a big difference
between a blank message (b'') and no message - b'TOPIC #channel' is a
request for the topic while b'TOPIC #channel :' clears the topic since
the part after the b':' is b'' (b'TOPIC #channel :Welcome to #channel'
sets the topic to "Welcome to #channel"). In my code, I would have an
object representing a message rather than parsing it multiple times. If
the message
attribute is not None, I send b'{command} {args} :{message}', otherwise
b'{command} {args}'. If I considered '' falsey, either I would require
all messages to have ":" (which would not actually be part of the
message) or have any request to view the topic as a channel op clear the
topic. This would apply to the server parsing the message as well. A few
other commands have messages optional as well, but they are not as
serious as TOPIC.

I could do:

if has_message:
send('{command} {args} :{message}')
else:
send('{command} {args}')

but then I'd have to make sure has_message stays accurate since message
won't necessarily be. Or maybe I could leave message undefined and catch
the appropriate exception. However, using None is the cleanest and most
obvious.

I know Rick likes to troll, but I do agree with him that "if something:"
for arbitrary objects can be ambiguous or confusing. I don't think
if/while must have True or False, but not every object has an obvious
truth value.
 
C

Chris Angelico

I could do:

if has_message:
send('{command} {args} :{message}')
else:
send('{command} {args}')

but then I'd have to make sure has_message stays accurate since message
won't necessarily be. Or maybe I could leave message undefined and catch
the appropriate exception. However, using None is the cleanest and most
obvious.

I know Rick likes to troll, but I do agree with him that "if something:"
for arbitrary objects can be ambiguous or confusing. I don't think
if/while must have True or False, but not every object has an obvious
truth value.

Using None when '' is a valid token is the right thing to do (see
also, for instance, SQL's NULL, which is often used the same way). And
then you have gone away from the language's idea of whether a string
is truthy or not, so you get explicit:

if message is not None:
send('{command} {args} :{message}')

Easy! Or if the language went the other way ("all strings are true"),
it would be the other way around, you'd use "if message!=''" for the
other case. It's not a difficult problem.

Carry on bikeshedding. :)

ChrisA
 
S

Steven D'Aprano

I can see how truth testing for empty values is convenient, but perhaps
objects should only have a truth value if explicitly given one -
particularly in cases where such a value wouldn't be obvious or the
obvious value isn't the actual one:

You have run into Python's default behaviour: objects are treated as
something by default. If you want them to represent nothing, you have to
explicitly code that case.

py> o = object()
py> bool(o)
True

Yet again, thinking about something versus nothing instead of true/false
makes the behaviour both obvious and correct: of course an object should
be treated as something (true-like) rather than nothing (false-like) by
default, it's an *object* is it not?

If you want your type to implement non-default semantics, such as
container semantics, then you need to code it.

{}

Would it not be reasonable to expect an empty namespace to have a truth
value of False since [] and friends do? It's a bit of a gray area for an
object defined by "class C: pass", but this is *specifically intended*
to be a namespace. Why should things like functions or exceptions have
truth values?

And this is a good life-lesson that any class called "SimpleFoo" will not
stay simple for long.

If you are right that SimpleNamespace should be treated as a container,
then it should implement container semantics. Since it doesn't, that is
either:

1) a bug; or
2) a triumph of laziness over correctness

I imagine though that the Python dev's answer will basically be #2: "it
isn't a container, it just behaves a little bit like a container, except
when it doesn't" kinda thing. But feel free to report it as a bug and see
what happens.

(This is not *entirely* wrong, because SimpleNamespace certainly doesn't
*claim* to be a container, nor does it expose the full container API. But
it should, even if that means it is no longer quite so simple.)
 
R

Ranting Rick

I imagine though that the Python dev's answer will basically be #2: "it
isn't a container, it just behaves a little bit like a container, except
when it doesn't" kinda thing.

The emperor has no clothes!
 
D

Devin Jeanpierre

isempty is not a container method.

Your entire reply is predicated on this idea that I was talking about
writing classes with this extra "isempty" method.

No. I was talking about having "isempty" be part of the collection
interface, and eliminating polymorphic bool conversion.

If you were confused, instead of assuming I meant something patently
absurd, you should have asked for a clarification.

-- Devin
 
S

Steven D'Aprano

I have a better real example, but I opted not to use it before since it
requires some explanation - IRC messages. A client-to-server message has
the basic form of b'COMMAND arguments :message' (e.g. b'PRIVMSG #channel
:hi guys!'). Some commands have no message part because there is no
message associated with it (e.g. b'JOIN #channel') and there is at least
one where there is a big difference between a blank message (b'') and no
message - b'TOPIC #channel' is a request for the topic while b'TOPIC
#channel :' clears the topic since the part after the b':' is b''
(b'TOPIC #channel :Welcome to #channel' sets the topic to "Welcome to
#channel").

Okay, so you have two distinct "nothing" states when considering the
message part of an IRC command: the empty string, and missing.

That's okay. Floats have two zeroes (+0.0 and -0.0); complex numbers have
four. (Although they try hard to hide that distinction from you.)

There's nothing that says that you can only have a single falsey value in
a type, or that you might not sometimes wish to distinguish between
different false-like states. You need to distinguish between the many
different true-like messages, so you should not be surprised that you
need to distinguish between two false-like messages.

There are many ways to implement this. Here are just the most obvious:

1) a Command object where the message attribute is optional, but if
present, it is always a string;

2) a Command object where the message attribute is always present, but
can be a string or some non-string sentinel value (e.g. None);

3) a string, where the message attribute is determined by the location
of the colon, if any

4) a tuple with either two or three fields: (command, channel [,message])


In my code, I would have an object representing a message
rather than parsing it multiple times. If the message
attribute is not None, I send b'{command} {args} :{message}', otherwise
b'{command} {args}'.

Clear and obvious. Nothing wrong with that.

I could do:

if has_message:
send('{command} {args} :{message}')
else:
send('{command} {args}')

but then I'd have to make sure has_message stays accurate since message
won't necessarily be.

Yes, keeping a separate variable is a mug's game. Encapsulate it in the
Command object, and have the Command object responsible for keeping it in
sync (assuming it is mutable), or just make Command immutable and be done
with it.

Or maybe I could leave message undefined and catch
the appropriate exception. However, using None is the cleanest and most
obvious.

Yes it is. What's your point?

You've discovered a real-world situation where you can't collapse the
entire universe of valid values into just two, True and False, without
losing information. Did you think that this would be surprising?

Python developers often talk about interpreting objects "in a boolean
context" -- that's a pretty big hint that the semantics are to collapse
the value into two states. If you need three (or four, or fifty)
distinguishable states, then obviously boolean context will not solve
your problem. I never said it would.
 
D

Dennis Lee Bieber

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

cost= 3.75
print( cost )
Ah, but to some of us, "print" itself implies "convert argument to
human readable string format"... ie, an explicit "cast".
 
A

Andrew Berg

If you are right that SimpleNamespace should be treated as a container,
then it should implement container semantics. Since it doesn't, that is
either:

1) a bug; or
2) a triumph of laziness over correctness

I imagine though that the Python dev's answer will basically be #2: "it
isn't a container, it just behaves a little bit like a container, except
when it doesn't" kinda thing. But feel free to report it as a bug and see
what happens.
I'm not saying it's necessarily wrong, but I do think it quacks a lot
like a container, even though it isn't one, especially if you're
listening for quacks instead of looking for ducks.
 

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,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top