question about True values

J

J. Clifford Dyer

Steven said:
On Sat, 28 Oct 2006 03:13:42 +0100, Steve Holden wrote:
>

First off, even though nobody has called me on it, this example really
prints "thank you", not "bugger off". I got confused in my cutting and
pasting. Sorry about that.

Cliff is making a point about semantics, and he's absolutely correct about
it, although it is irrelevant since we're talking about two-value logic
not semantics.

Thank you for the clarification Steven (D'Aprano). To a certain level,
I agree that semantics are important. I hesitated about including that
example in my post to begin with. However, my point, and hopefully I'll
be able to make it more clearly now, was that true/false is a useful way
to think regarding logical statements like x == 3, but that when you are
dealing with strings, or more accurately strings that represent
language, you really aren't talking about truth any more, because in
that context, truth cannot be divorced from semantics--somethingness, on
the other hand, can.

Conceptually, you have to go through "'something' is true, and 'nothing'
is false" before it makes sense.

On the other hand, (to play devil's advocate for a moment), when you are
dealing with comparison operators, you have to go through "true
statements yield something and false statements yield nothing" before it
makes sense, or rather "yield a nothing value." So either way you think
about it, you have to, in some cases, mentally convert from truthiness
to somethingness or vice versa. I don't find it as odious to mentally
convert the comparison operators as I do the declarative statements. I
don't think it's just a personal preference either, because that way
your mental processes are in sync with the way python works. It
evaluates if the statement is true and yields a something value, and a
nothing value if it's false. You are working with the Tao of Python, if
you'll forgive the analogy. However thinking in terms of truth and then
saying that all statements that exist are true works, but runs counter
to what is going on behind the scenes.

Or maybe it doesn't, if everything's getting converted implicitly to
bool anyway. Maybe it is just personal preference after all. But then
bools are assigned "something" and "nothing" values....

This stuff is tricky, but I'm enjoying trying to wrap my mind around it,
and appreciating the comments and critiques.

Cheers,
Cliff
 
S

Steven D'Aprano

Not all objects that have a state of emptiness consider emptiness to be
false.

In which case they should define __nonzero__ appropriately.

In which case, calling code that assumes that len(obj) is a substitute for
truth-testing will do the wrong thing.

No, it often shouldn't.

Okay, I want to qualify my statement: if a class doesn't define
__nonzero__ or __len__, *and doesn't want the default Python behaviour of
all instances evaluating as True*, then they should.
A. It's not always desirable for empty to be false. Numpy defines a
bunch of numeric array types that raise an exception when __nonzero__
(actually nb_nonzero in the C API) is called. This is the correct
behavior for numpy. It makes no sense for numpy arrays to be used in a
boolean context, but they certainly can be empty.

And, appropriate to the class, numpy arrays raise an exception when
__nonzero__ is called -- just as they should.


B. Sometimes it's impossible to determine the state of emptiness. For
example, iterators.

Since Guido has ruled that the protocol is that all iterators are True,
there is no need for __nonzero__ since the default behaviour does the job.


[snip]
You haven't been paying attention.

Yes, this behavior has been changed in 2.5. The built-in iterators
always return True in 2.5. But even in 2.4, it was not true for
iterators in general:

Yes, you are right. I was fooled by a coincidence.
At no point could you count on an iterator returning False for empty,
unless you'd made it yourself. Neither "if a:" nor "if len(a)>0" is a
valid test for an empty iterator.

Correct. Guido's decision is that iterators are always "Something" (that
is, True in a truth context) even if they are exhausted.

It doesn't really matter. dicts and sets don't have a length, either.
Or if they do, it's the length of the hash table, not the number of
entries. Weren't you taught that? But Python uses len() to get the
number of items in a container. Any Pythonic implementation of a binary
tree class would use len() to return the number of entries in it.
Anything that uses indexing ought to define len(), if it can.

In the binary tree:

tree --> A
A.left --> B, A.right --> C

what's tree[0]? Should it be A (preorder), or B (inorder) or C (postorder)?

Overall, your objections don't really apply, since you're arguing what
ought to be whereas my argument is pragmatic. Practically speaking, in
realistic situations, "if len(a)>0" will work for a wider range of types
than "if a:".

Well, that's a quantitative claim you're making there. Have you
actually gone through, say, the built in types and checked how many
have a length versus how many work in a truth-context?
.... print "Modules work with bool"
....
Modules work with bool.... print "Modules work with len"
....
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: len() of unsized object

"if len(a)>0:" only works for objects that define __len__.

"if a:" works for any object that doesn't go to the trouble of
specifically prohibiting it, as numpy arrays deliberately do. That's the
Python way, and it is a deliberate design.
 
C

Chetan

Steven said:
First off, even though nobody has called me on it, this example really prints
"thank you", not "bugger off". I got confused in my cutting and pasting.
Sorry about that.


Cheers,
Cliff

I am joining after some network downtime here, so I seem to have missed what
the real issue here is. At the risk of being completely irrelevant to the
discussion here, I think it doesn't seem to be just about something or
nothing - is None something or nothing? It seems to be neither:
True

Chetan
 
C

Chetan

Gabriel Genellina said:
A "true" Boolean value should not be coerced into any other thing. True+1 is as
meaningless as "A"+1, or even "1"+1. The fact is, bool is just an integer in
disguise.
I always regretted that Python just went mid-way moving onto a true Boolean
type; I'd prefer it to stay as it was before bool was introduced.



It *is* an integer with a hat on.

True
This has focussed on the relational operators, which seem to produce a number
with a hat on. However, logical operations do not do so.
True and "This string" produces "This string", for example.

This is hardly surprising, though.
The way they are defined, booleans seem to be the syntactic sugar that some
people like. Many of the projects that I have been associated with had such
a define because the language (C) does not provide it. On the other hand,
there are many who do just fine without them.

For expressions used in control flow, if the expression somehow produces a 0
(False included) or None, the behavior is as if the expression is false.
Confusion may arise because the way that answer is produced may not be
immediately obvious. For constructs such as "if obj:" the answer depends on
which methods the object has defined and what the state of the object is at the
time, but this has nothing to do with whether the answer that was produced was
strictly a boolean or not.

Chetan
 
C

Chetan

Georg Brandl said:
If is, of course, nothing. You may have misunderstood the semantics of the
"and" and "or" operators.

I have not. I just posted another message on the subject. All I am trying to
point out is that the "nothingness" evaluation does not occur at the level of
expressions. It is only when the expression is needed to make decisions about
control flow that this comes into picture.
 
G

Georg Brandl

Chetan said:
I am joining after some network downtime here, so I seem to have missed what
the real issue here is. At the risk of being completely irrelevant to the
discussion here, I think it doesn't seem to be just about something or
nothing - is None something or nothing? It seems to be neither:

If is, of course, nothing. You may have misunderstood the semantics of the
"and" and "or" operators.

x and y | x something | x nothing
---------------------------------------
y something | y | x
y nothing | y | x

x or y | x something | x nothing
---------------------------------------
y something | x | y
y nothing | x | y


Georg
 
C

Carl Banks

Steven said:
Carl Banks:

Well, that's a quantitative claim you're making there. Have you
actually gone through, say, the built in types and checked how many
have a length versus how many work in a truth-context?

No, and it's irrelevant to my argument.

For some reason, people seem to think it's absolutely wonderful that
you can write "if X:" somewhere, and that this "works" whether X is a
list or an int or any other object, as if "working" for both ints and
lists was actually useful.

Well, it's not.

You see, presumably you have to do something with X. And in realistic
code, there's not a lot of stuff you can do with that works for both
container types like lists and dict, and atomic types like ints and
floats. All you could do is call generic object protocol stuff like
id() or str(). There's some odd operations that can work for both a
container and an atomic (addition); even so, it's rare that a given
piece of code would actually be useful for both containers and atomics.
(I mean, you could pass lists into a function such as the following,
but would it be useful?)

def binomial3(a,b,c,d):
return a + 3*b + 3*c + d

What I'm saying is, the fact that "if X:" "works" for almost any type
is big ol' red herring. The only thing that really matters is if it
works for types that _actually have some realistic overlapping uses_.

So let's do your little exercise keeping that in mind. Let's look at
realistic uses of containers. Suppose we have a function such as this:

def f(X):
if not X:
return
do_some_indexing(X[2],X[4])

We see that we subscript X. Now, you can't subscript an int, so if
we're going to count number of types that support "if a:" versus the
number that support "if len(a)>0", we're not going to consider ints.
When deciding whether we should go with "if a:" or "if len(a)>0", we
should only count types that support indexing.

Objects that support indexing and work with "if a:":
lists,tuples,dicts,strings,xrange,collections.deque,array.array

Objects that support indexing and work with "if len(a)>0":
lists,tuples,dicts,strings,xrange,collections.deque,array.array ...
numpy arrays

Another thing containers do a lot is iteration.

Objects that support iteration and work with "if a:"
lists,tuples,dicts,sets,strings,xrange,collections.deque,array.array

Objects that support iteration and work with "if len(a)>0:"
lists,tuples,dicts,sets,strings,xrange,collections.deque,array.array
.... numpy arrays

What can we conclude? If you're indexing or iterating, "if a:" and "if
len(a)>0:" are equally successful for built-in types, but "if
len(a)>0:" also works for numpy arrays. If your only measure of
success is how many classes the emptiness tests works for, then "if
len(a)>0:" wins. This is just an example; we haven't looked at all
possible uses of containers. There will be a few cases where "if a:"
works but "if len(a)>0:" doesn't. But it should be pretty clear where
this is headed.

So the question is: Do you want use "if len(a)>0:", so that in a lot
of cases your code can also work if someone wants to use a popular
third party package? Or do you want to use "if a:", so that in rare
cases your code could also work with some builtin atomic types?


Carl Banks
 
G

Georg Brandl

Chetan said:
I have not. I just posted another message on the subject. All I am trying to
point out is that the "nothingness" evaluation does not occur at the level of
expressions. It is only when the expression is needed to make decisions about
control flow that this comes into picture.

This is not correct. "and" and "or" involve truth (or "somethingness")
evaluation, as you can see from this example:

Python 2.5 (r25:51908, Sep 22 2006, 10:45:03).... def __nonzero__(self):
.... print "nonzero"
.... return True
....
nonzero
1

Georg
 
S

Steven D'Aprano

No, and it's irrelevant to my argument.

And yet you not only made the claim in the first place, but you also spend
a good half or three quarters of your response justifying your claim. You
obviously didn't think it was irrelevant when you first made the claim,
and you clearly still don't think it was irrelevant now that you've spent
time checking an arbitrary set of types. Either way, you're still wrong.

"if a:" will work with EVERY TYPE except those few (the only one?) like
numpy arrays which deliberately raise an exception because they don't make
sense in a Boolean context. (And even then, arguably numpy is doing the
wrong thing: the Pythonic behaviour would be for numpy arrays to all
be True.)

"if len(a)>0:" can only work with types that have lengths. It is a logical
necessity that the number of types that have lengths must be no bigger
than the number of types in total, numpy arrays notwithstanding.


For some reason, people seem to think it's absolutely wonderful that
you can write "if X:" somewhere, and that this "works" whether X is a
list or an int or any other object, as if "working" for both ints and
lists was actually useful.

Well, it's not.

You see, presumably you have to do something with X. And in realistic
code, there's not a lot of stuff you can do with that works for both
container types like lists and dict, and atomic types like ints and
floats.

Who cares whether or not your function accepts mixed data types like lists
and floats? Why do you think it matters whether the one function has to
operate on both sequences and atomic types?

What *does* matter is that, regardless of whether you are operating on
sequences, mappings, numeric types, the same syntax works.

That's why (for example) Python allows + to operate on lists, or strings,
or floats -- but that doesn't imply that you can add a list to a float,
or that the same code will operate happily on both lists and floats. You,
the developer, don't need to care what data types you are adding, you just
use the same syntax: a+b. The objects themselves know what addition
means to themselves. In practice, string addition threatens to be so
inefficient that you might wish to avoid doing it, but as a general
principle, Python idioms and syntax are as type-independent as possible.

That's why obj[index] works whether you are getting or setting or deleting
an item, whether it is a sequence or a mapping or a custom class. As much
as possible, you don't need to know what the object is to know what syntax
to use. The idiom for item-access should always be obj[index], not
obj[index] for some types, obj.get(index) for some others, and
getitem(obj, index) for the rest.

(Custom classes are, of course, free to break these guidelines, just as
the numpy developers were free to have __nonzero__ raise an exception. One
hopes they have good reasons for doing so.)

And that's why Python not only allows but prefers "if any_object_at_all:"
over type-dependent tricks. The *object itself* is supposed to know
whether it is equivalent to true or false (or, in rare cases like numpy
arrays, raise an exception if truth-testing doesn't mean anything for the
type). You, the developer, are not supposed to spend your time wondering
how to recognise if the object is equivalent to false, you just ask the
object.

If some well-meaning person argued that the correct way to test for a
numeric value of zero/non-zero was like this:

if x*2 != x:
# x must be non-zero

you'd fall down laughing (at least, I hope you'd fall down laughing). Yes,
such a test works, but you'd be crazy to do such unnecessary work if all
you want to know if x was nonzero. (And beware of floating point rounding:
what if x is tiny?)

And then someone passes a class that implements infinity or the alephs to
this function (that could simply mean an INF float on a platform that
supports the IE standard) and the code wrongly decides that INF is zero.
Oops.

"if x == 0:" is better, but still not good enough, because you're
still making assumptions about the data type. What if it is a numeric type
that works with interval arithmetic? You don't know what the right way to
test for a False interval is -- but the interval class itself will know.

And that's the point -- you're making unnecessary assumptions about the
data, *and* doing unnecessary calculations based on those assumptions. At
best, you're wasting your time. At worst, you're introducing bugs.

It isn't often that I make an appeal to authority, but this is one of
them. No offense, but when it comes to language design its a brave or
foolish programmer who bucks the language idioms that Guido chose.
 
C

Carl Banks

Steven said:
It isn't often that I make an appeal to authority, but this is one of
them. No offense, but when it comes to language design its a brave or
foolish programmer who bucks the language idioms that Guido chose.

Well, it's pretty clear you consider some abstact notion of unity of
style more important than practical considerations of how your data is
used.

In that case you might as well just go with what the authority tells
you to.

(And hope that your users don't do much numerical stuff.)


Carl Banks
 
A

Antoon Pardon

On Sun, 29 Oct 2006 00:31:23 -0700, Carl Banks wrote:

That's why (for example) Python allows + to operate on lists, or strings,
or floats

That was IMO a mistake. There are types for which concatenation as well as
addition are meaningfull operators. By using the "+" for both these
operations, writing such a class becomes awkward in Python because you
have to stray away from Python idiom for one of the two operations.
Should Python have chosen the "_" character for concatenating writing
amnd working with such a class would have looked more natural.
-- but that doesn't imply that you can add a list to a float,
or that the same code will operate happily on both lists and floats. You,
the developer, don't need to care what data types you are adding, you just
use the same syntax: a+b.

Of course the developer needs to care. It's not because a+b will give
a result that the result will also be meaningfull. It is not because
a class has defined the operators "+", "-", "/", "*" it will give
a meaningfull result if you throw a matrix of instances into procdure
that solves equations.
The objects themselves know what addition
means to themselves. In practice, string addition threatens to be so
inefficient that you might wish to avoid doing it, but as a general
principle, Python idioms and syntax are as type-independent as possible.

Which isn't necesarrily a good thing. I think there is even a warning
somewhere against too easily overloading the arithmetic operators
for operations that don't act at all as such operators. I think
they should have thought of that before using "+" for concatenation.
That's why obj[index] works whether you are getting or setting or deleting
an item, whether it is a sequence or a mapping or a custom class. As much
as possible, you don't need to know what the object is to know what syntax
to use. The idiom for item-access should always be obj[index], not
obj[index] for some types, obj.get(index) for some others, and
getitem(obj, index) for the rest.

(Custom classes are, of course, free to break these guidelines, just as
the numpy developers were free to have __nonzero__ raise an exception. One
hopes they have good reasons for doing so.)

And that's why Python not only allows but prefers "if any_object_at_all:"
over type-dependent tricks. The *object itself* is supposed to know
whether it is equivalent to true or false

But the objects idea of true and false may not be the most usefull
distinction the programmer wants to make. Look at the following
example:

if container:
Prepare()
Treat(container)

Now Treat will throw an exception if container is not enough
list like. But that exception will be throw after Prepare has
done useless work in this case. If I replace "if container"
with "if len(container)" the exception will be thrown earlier.
So using "if len(container)" gives more usefull information
than a simple "if container".
(or, in rare cases like numpy
arrays, raise an exception if truth-testing doesn't mean anything for the
type). You, the developer, are not supposed to spend your time wondering
how to recognise if the object is equivalent to false, you just ask the
object.

If some well-meaning person argued that the correct way to test for a
numeric value of zero/non-zero was like this:

if x*2 != x:
# x must be non-zero

you'd fall down laughing (at least, I hope you'd fall down laughing). Yes,
such a test works, but you'd be crazy to do such unnecessary work if all
you want to know if x was nonzero. (And beware of floating point rounding:
what if x is tiny?)

And then someone passes a class that implements infinity or the alephs to
this function (that could simply mean an INF float on a platform that
supports the IE standard) and the code wrongly decides that INF is zero.
Oops.

"if x == 0:" is better, but still not good enough, because you're
still making assumptions about the data type. What if it is a numeric type
that works with interval arithmetic? You don't know what the right way to
test for a False interval is -- but the interval class itself will know.

But you have no idea that the interval of "False" will guide you to the
right branch. And inteval class might treat all zero length intervals
as False or it could have a special empty interval which would be the
only False interval. Both could be argued for, but you would want to
know which before you would rely on "if some_interval:" to guide
you to the right branch.
And that's the point -- you're making unnecessary assumptions about the
data, *and* doing unnecessary calculations based on those assumptions. At
best, you're wasting your time. At worst, you're introducing bugs.

You have to make assumptions anyway. And your kind of code could
introduce bugs just the same.
It isn't often that I make an appeal to authority, but this is one of
them. No offense, but when it comes to language design its a brave or
foolish programmer who bucks the language idioms that Guido chose.

You don't have to be that brave to go against Guido's autority. It
is only recently he introduced a ternary operator and then only after
someone in the development group was hit by a bug introduced by the
then supported idiom. Python is a nice enough language with a high
quality in design. Yet it still has its warts. So I see no
reason to blindly follow the autority of its designers.
 

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,774
Messages
2,569,598
Members
45,160
Latest member
CollinStri
Top