question about True values

A

Antoon Pardon

But in this specific instance, I don't see any advantage to explicitly
testing the length of a list. Antoon might think that is sufficiently
polymorphic, but it isn't. He cares whether the object has zero _length_,
but for true polymorphism, he should be caring about whether the object is
_empty_. Not all empty objects have zero length, or even a length at all.
(E.g. binary trees.) That's why Python classes can use a __nonzero__
method, falling back on __len__ only if __nonzero__ is not defined.

Nobody can force you to write a container class that also provides a
__len__ method. But if you don't provide one then IMO it is your class
that deviates from standard practice. Mathematically, sets and
directories have no length either, yet the len function does provide
an answer if you give it a set or directory as an argument. So
it seems that python has generalised the len function to provide
the number of elements in the container.

I have written a Tree class(*). It can be used as a drop in replacement
anywhere where a directory is used, as long as there is a full order
relationship in the key domain. That tree class provides a __len__
method that will anser with the number of items in the tree just
as a directory would and I see nothing wrong with that.

Of course I can't account for all possible ways someone wishes to
write a class, but I don't see what is wrong with counting on
the fact that an empty container has a length of zero.

I can write a container class where the truth value of an object
is independent of whether or not the object is empty and then
the "if obj:" idiom will fail to provide true polymorphism too.

(*) http://www.pardon-sleeuwaegen.be/antoon/avltree.html
 
A

Antoon Pardon

I think it is a good time to remind people of some extremely well-thought
out opposition to the introduction of bools to Python from Laura Creighton:

http://mail.python.org/pipermail/python-list/2002-April/095878.html

She lost the debate, Guido had the final word and Python now has bools.
Take particular note of her description of Python distinguishing between
Something ("cat", 4, [0, 1, 2] etc) and Nothing ("", 0, [] etc).

Yes and IMO that is a less usefull distinction than the distinction
between True and False. When I write code I think in terms of
conditions. In those conditions this has to be treated this way
otherwise it has to be treated an other way. Conditions give
results that are either True or False, not Something or Nothing.

And if you read the thread that Laura Creighton's post is part of, you
will see that she acknowledges that most people think strongly in terms
of binary true/false yes/no states, and that this is often the wrong thing
to do. There are lots of human thought patterns that are unhealthy, and
binary is often one of them.

The Nothing/Something dichotomy is just as binary as the True/False
dichotomy. And in the case of deciding whether you will take the
"then" branch or "else" branch I see nothing unhealthy in checking
whether the condition was true or not.
The world is continuous, and our brains think in binary. No wonder people
have such trouble with concepts like evolution:

The world may be continuous, programming structures are discrete.
You take the "then" branch or not. In the latter case you can
again decide to take the elif branch or not. There is nothing
continuous in that.
- there is a continual chain of individuals such that every offspring of
an reptile is a reptile, and every parent of a mammal is a mammal, and
yet mammals are directly descended from reptiles.

By I digress. This is too easy to get off topic...



Not at all, you got your operators the wrong way around. Five certainly is
less than 10 in every counting system I've ever come across. I think you
meant 5 > 10 is Nothing.

Yes that was a type, I meant I don't think of 5 > 10 as Nothing.
Certainly purely mathematical relations like GT and LT lend themselves
very well to true two-valued algebra. The thing to remember is that
Python's truth model is not the same as pure Boolean algebra. For
starters, it certainly is not two-valued! It is infinitely-valued. It's
just that many of those values are equivalent *in a Boolean context*.

That it is infinitely-valued is a red herring. It is partitioned in
two, so all these infinite values are mapped into two possibilities
because at the end you have to decide a simple yes/no question.
Will I repeat the loop or not. Will I take the "then" branch or
not.
In Pascal, writing "x := 2; if x then..." would be an error, because x is
not a Boolean. But it is certainly useful to be able to write the
equivalent in Python. The designer of Pascal choose strict Boolean
algebra; the designer of Python choose a more flexible, less strict model.

If you are going to argue for strict Booleans, like in Pascal, then
mathematical relations like GT and LT will be your poster-child.

But if you are going to argue for Python's less strict truth model, then
you'll talk about lots of examples like this:

if somelist:
# work with the list
else:
# nothing to work with

In most case I just do:

for el in somelist:

which works for empty lists just as good as non-empty ones. So
why should an empty list be treated differently from an non-empty
one if it is used in a boolean context?
If you read Laura's post, you will see that she is arguing strongly that
thinking about True and False is often -- usually! -- a mistake.

Yes she argues that? So? I think she is wrong and her argument
lacking.
I
acknowledge that there are cases where it is either necessary or desirable
to think in terms of True/False, but that is less common than you might
think.

Control structures in programming languages do nothing else but decide
things in terms of True/False. You may dress things up but in the
end a statement like:

if obj:

Will evaluate/map into a two valued domain, where one value will lead you
into the "then" branch and the other value will not. And IMO the more
clearer the relationship between the written code and the final result
the better.
 
A

Antoon Pardon

Thanks! I rest my case!


Well, would you declare numbers less than 100 False?

No but the condition: x > 100, will map all numbers to
either True or False. Can you provide a condition that will
provide a mapping to Nothing and Something? Without
artificially mapping False to Nothing and True to Something?
Think about it in more philosophical terms. What is Truth?
The Internet Encyclopedia of Philosophy may be some help
with this - http://www.iep.utm.edu/t/truth.htm

I don't care about such philosophical issues. I also
doubt that you could provide better answers if you
would subsituted Somthingness for truth is that text.
Then when you get tired of that, suppose that "if" and
"while" are asking for "yes" and "no", instead of "true"
and "false", and ask yourself if we have the philosophical
problems with "yes" that we do with "true".

Of course we have. Deciding whether a statment/condition is true
or not is equivallent to deciding whether or not we should answer
yes or no to a related question.
 
C

Carl Banks

Steven said:
But in this specific instance, I don't see any advantage to explicitly
testing the length of a list. Antoon might think that is sufficiently
polymorphic, but it isn't. He cares whether the object has zero _length_,
but for true polymorphism, he should be caring about whether the object is
_empty_. Not all empty objects have zero length, or even a length at all.
(E.g. binary trees.)

Conversely, not all objects that have length consider zero-length to be
false. Whether you test with "if a:" or "if len(a)>0", some objects
are going to be denied.

Thing is, objects that don't have length have almost no overlapping
uses with lists (i.e., you'd hardly ever write a function that could
take an int or a list, unless you type check the argument or use only
object protocol stuff like id and getattr, or pass it to another
function that does the same). Iterators do have overlapping uses with
lists, but the "if a:" doesn't work for them, so it's moot. OTOH,
objects that have length but don't consider zero-length to be false
(numpy arrays) do have overlapping uses with lists.

So, as a practical matter, I'd be inclined to side with Antoon on this
issue, even if it only increases polymorphism for certain people.

"if a:" almost never increases polymorphism because almost no
lengthless objects would work in that function anyways. Even if you
don't use numpy arrays, there's little practical benefit of "if a:"
except to save typing. If you do use numpy, it limits polymorphism.

"if len(a)>0" does increase polymorphism because it allows for objects
that have length but don't equate empty to false.

P.S. binary trees do have length: it's the number of nodes, just as the
number of keys is the length of a dict. I can't think of any objects
that use indexing but don't have a length, except for
poorly-implemented proxy objects. It's possible to define such types,
but would that be a Pythonic use of indexing?



Carl Banks
 
S

Slawomir Nowaczyk

On Fri, 27 Oct 2006 11:25:09 -0700

#> P.S. binary trees do have length: it's the number of nodes, just as
#> the number of keys is the length of a dict. I can't think of any
#> objects that use indexing but don't have a length,

Well, infinite lists (either circular or dynamically-growing) would be
one (unless you consider infinity to be a valid value of length, of
course).

Dictionaries with default value would be another (of course, Python 2.5
defaultdict *does* have length, but I would claim it is a wart).

But I agree those are pathological cases.

--
Best wishes,
Slawomir Nowaczyk
( (e-mail address removed) )

Programmer - A red-eyed, mumbling mammal
capable of conversing with inanimate objects.
 
J

J. Clifford Dyer

I do see how mapping to Truth/Falsehood is more natural, and I do
believe that one of the great things about python is that it feels
natural in so many ways, and hence makes it easy to produce code, but
the one thing that Ms. Creighton points out that I can't get past is
that Python, even with its bool type, *still* evaluates somethingness
and nothingness, and True and False are just numbers with hats on.
1

So when you say
.... print "Yes!"

Python is not evaluating the truth of the matter, but, as Ms. Creighton
would say, the "somethingness" of that which 10 > 5 evaluates to. (1
aka True)

Furthermore, how do you explain this bizarreness in terms of "Truth" and
"Falsehood?" You have to go back to the fact that True=1 and that
REALLY, Python is dealing with somethingness and nothingness. It might
not be as direct a mental connection as True/False, but it is certainly
a more accurate one for understanding how Python works.
True

Finally, while True/False is a good mental mapping for numeric
comparisons, take the following:
.... print "thank you"
.... else:
.... print "bugger off"

bugger off

Clearly this is not true. (Google Cliff/Dyer open source: only 11
hits.), but the string is *something* so the if block gets evaluated.

Cheers,
Cliff
 
S

Steve Holden

J. Clifford Dyer said:
I do see how mapping to Truth/Falsehood is more natural, and I do
believe that one of the great things about python is that it feels
natural in so many ways, and hence makes it easy to produce code, but
the one thing that Ms. Creighton points out that I can't get past is
that Python, even with its bool type, *still* evaluates somethingness
and nothingness, and True and False are just numbers with hats on.

1

So when you say

.... print "Yes!"
Seems pretty clear to me that the situations you discuss above involve
numeric coercions of a Boolean value.
Python is not evaluating the truth of the matter, but, as Ms. Creighton
would say, the "somethingness" of that which 10 > 5 evaluates to. (1
aka True)

It does seem that there is a specific type associated with the result of
a comparison, even though you would really like to to be "a number with
a hat on".
Furthermore, how do you explain this bizarreness in terms of "Truth" and
"Falsehood?" You have to go back to the fact that True=1 and that
REALLY, Python is dealing with somethingness and nothingness. It might
not be as direct a mental connection as True/False, but it is certainly
a more accurate one for understanding how Python works.

True
I have no idea what you think that you are demonstrating here.
Finally, while True/False is a good mental mapping for numeric
comparisons, take the following:

.... print "thank you"
.... else:
.... print "bugger off"

bugger off

Clearly this is not true. (Google Cliff/Dyer open source: only 11
hits.), but the string is *something* so the if block gets evaluated.
... print "You don't know what you are talking about"
... else:
... print "Sorry: of course you are perfectly correct"
...
You don't know what you are talking about
regards
Steve
 
G

Gabriel Genellina

Seems pretty clear to me that the situations you discuss above involve
numeric coercions of a Boolean value.

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 does seem that there is a specific type associated with the result of
a comparison, even though you would really like to to be "a number with
a hat on".

It *is* an integer with a hat on.
True


--
Gabriel Genellina
Softlab SRL

__________________________________________________
Correo Yahoo!
Espacio para todos tus mensajes, antivirus y antispam ¡gratis!
¡Abrí tu cuenta ya! - http://correo.yahoo.com.ar
 
S

Steven D'Aprano

It does seem that there is a specific type associated with the result of
a comparison, even though you would really like to to be "a number with
a hat on".

Python bools really are subclassed from ints:
True

They are ints wearing a Boolean hat. This was a controversial compromise.

I have no idea what you think that you are demonstrating here.

Run through the expressions by hand:

(1 > 0) < 1
True < 1
False

The mere fact that you can compare bools with ints demonstrates that
Python bools aren't "real" Booleans.

... print "You don't know what you are talking about"
... else:
... print "Sorry: of course you are perfectly correct"
...
You don't know what you are talking about

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.
 
S

Steven D'Aprano

Nobody can force you to write a container class that also provides a
__len__ method. But if you don't provide one then IMO it is your class
that deviates from standard practice. Mathematically, sets and
directories have no length either, yet the len function does provide
an answer if you give it a set or directory as an argument.

Do you mean dictionary?

Dictionaries, a.k.a. hash tables, aren't a standard mathematical data
type. Even if they were, they have two "lengths" (size really): the size
of the table, and the number of items in the table. In a high level
language like Python, you don't care about the size of the table (since it
will, I believe, automatically grow dynamically if you need it to), so
number of items is the only "size" (length) you could care about.

So
it seems that python has generalised the len function to provide
the number of elements in the container.

Sure. But what about a container where the number of elements isn't
well-defined, e.g. iterators? Or something like a binary tree, where
counting the number of items is relatively expensive, but telling whether
it is empty or not is cheaper than dirt?

Here's a real example: it can be expensive to count the number of files in
a directory -- on my PC, it takes almost a third of a second to count a
mere 15,000 files in a single directory. (There may be a more sensible
way of counting the number of files under Linux than ls | wc -l, but if
so I don't know it.) But why slog through 15,000 files if all you need to
know is if the directory is empty or not? As soon as you see one file, you
know it isn't empty. Stop counting! Who cares whether there is one file or
15,000 files?


I have written a Tree class(*). It can be used as a drop in replacement
anywhere where a directory is used, as long as there is a full order
relationship in the key domain. That tree class provides a __len__
method that will anser with the number of items in the tree just
as a directory would and I see nothing wrong with that.

And I'm happy for you. But imagine a container object that maps a URL to
some piece of data fetched from the Internet. Counting the size of the
Internet is infeasible -- even if you were willing to try, it could take
*weeks* of computation to determine! But one can certainly tell if there
is an Internet out there or not. Such an object would always be True,
unless you had lost network connectivity.

My container object will work perfectly well with "if internet" but not
with "if len(internet) > 0". You could even iterate over it, sort
of, by following links from one site to another.

But why are you checking the length of a container before iterating over
it? If you are writing something like this:

if len(container) != 0:
for item in container:
do_something()

then just stop it!


Of course I can't account for all possible ways someone wishes to
write a class, but I don't see what is wrong with counting on
the fact that an empty container has a length of zero.

Because you shouldn't assume containers have well-defined lengths unless
you actually care about the length, and you shouldn't assume that length
of zero implies "nothing to see here" unless you *know* that this is the
case. You should leave defining empty up to the container class itself.
Otherwise, you might be right 99 times in a hundred, but that hundredth
time will bite you.

I can write a container class where the truth value of an object
is independent of whether or not the object is empty and then
the "if obj:" idiom will fail to provide true polymorphism too.

A class that deliberate breaks the semantics of Python truth testing just
for the sake of breaking code really is a pathological case. Polymorphism
doesn't mean "will work with anything without exception".
 
S

Steven D'Aprano

No but the condition: x > 100, will map all numbers to
either True or False. Can you provide a condition that will
provide a mapping to Nothing and Something? Without
artificially mapping False to Nothing and True to Something?

A > B maps to max(0, A-B)

or more verbosely, "Remove B items from A items, stopping when there are
no more items to remove. What is left over? Something or Nothing?"

Likewise, A < B maps to max(0, B-A).
 
S

Steven D'Aprano

Conversely, not all objects that have length consider zero-length to be
false.

Which is why truth-testing is defined by __nonzero__ with __len__ only the
fall-back, for convenience.
Whether you test with "if a:" or "if len(a)>0", some objects
are going to be denied.

If a class doesn't define __nonzero__ or __len__, it should. Unless it is
meant to always be True.

Thing is, objects that don't have length have almost no overlapping
uses with lists (i.e., you'd hardly ever write a function that could
take an int or a list, unless you type check the argument or use only
object protocol stuff like id and getattr, or pass it to another
function that does the same). Iterators do have overlapping uses with
lists, but the "if a:" doesn't work for them, so it's moot.

Sure it works for iterators.
it = iter([0])
bool(it) True
it.next() 0
bool(it)
False

Perhaps this behaviour has changed in version 2.5, if so, I'd like to hear
the rationalisation before I declare it a mistake.

P.S. binary trees do have length: it's the number of nodes, just as the
number of keys is the length of a dict.

I don't know what you were taught, but I was taught that binary trees have
height and breadth. "Length" was never used for the number of nodes. If
you go to the trouble of walking the tree, you obviously get the
equivalent of a list with a length, but in all the texts I've read,
walking the entire tree is the classic example of an expensive operation
that you should try to avoid if you don't need to. The same goes for
counting the number of nodes, unless you wanted to cache the result
somewhere.
 
C

Carl Banks

Steven said:
Which is why truth-testing is defined by __nonzero__ with __len__ only the
fall-back, for convenience.

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

If a class doesn't define __nonzero__ or __len__, it should.

No, it often shouldn't.

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.

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

Thing is, objects that don't have length have almost no overlapping
uses with lists (i.e., you'd hardly ever write a function that could
take an int or a list, unless you type check the argument or use only
object protocol stuff like id and getattr, or pass it to another
function that does the same). Iterators do have overlapping uses with
lists, but the "if a:" doesn't work for them, so it's moot.

Sure it works for iterators.
it = iter([0])
bool(it) True
it.next() 0
bool(it)
False

Perhaps this behaviour has changed in version 2.5, if so, I'd like to hear
the rationalisation before I declare it a mistake.

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:
True

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.

I don't know what you were taught, but I was taught that binary trees have
height and breadth.

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.


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:".


Carl Banks
 
S

Steven D'Aprano

Steven said:
Iterators do have overlapping uses with lists, but the "if a:" doesn't
work for them, so it's moot.

Sure it works for iterators.
it = iter([0])
bool(it) True
it.next()
0
bool(it)
False

It works for *this* iterator. By accident.

Blimey, you're right. That can't be good.

In fact, it made a certain BDFL pretty mad:

http://mail.python.org/pipermail/python-dev/2005-September/056594.html

Okay, so all iterators are intentionally *supposed* to be True, always,
even if they are exhausted. As Guido says, don't treat iterators as
containers.

Fair enough.
 
A

Antoon Pardon

A > B maps to max(0, A-B)

or more verbosely, "Remove B items from A items, stopping when there are
no more items to remove. What is left over? Something or Nothing?"

This mapping no longer works if you are not working with numbers.
A = [1,2]
B = [3]
A > B False
max(0, A - B)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: unsupported operand type(s) for -: 'list' and 'list'
 
A

Antoon Pardon

Sure. But what about a container where the number of elements isn't
well-defined, e.g. iterators?

I see you already discovered iterators won't help you here.
Or something like a binary tree, where
counting the number of items is relatively expensive, but telling whether
it is empty or not is cheaper than dirt?

Counting the items in a binary tree and a dictionary are about equally
expensive. I guess that in practice a count attribute will keep score.
Here's a real example: it can be expensive to count the number of files in
a directory -- on my PC, it takes almost a third of a second to count a
mere 15,000 files in a single directory. (There may be a more sensible
way of counting the number of files under Linux than ls | wc -l, but if
so I don't know it.) But why slog through 15,000 files if all you need to
know is if the directory is empty or not? As soon as you see one file, you
know it isn't empty. Stop counting! Who cares whether there is one file or
15,000 files?

I don't know why you consider this a real example. The only time I care
whether a dictionary is empty or not is if I want to remove it and in
that case it better to just try to remove the dictionary and catch
the exception. Testing for emptyness or counting files in a directory
isn't robust anyway. You never know whether or not some other process
created a new file or deleted one between the time you tested and
the moment
And I'm happy for you. But imagine a container object that maps a URL to
some piece of data fetched from the Internet. Counting the size of the
Internet is infeasible -- even if you were willing to try, it could take
*weeks* of computation to determine! But one can certainly tell if there
is an Internet out there or not. Such an object would always be True,
unless you had lost network connectivity.

Indeed and you could loose connectivity between your testing for it and
making your first connection and your URL's turn bogus
My container object will work perfectly well with "if internet" but not
with "if len(internet) > 0". You could even iterate over it, sort
of, by following links from one site to another.

And in what way exactly would this container object of yours be
compatible with code that would expect a list like object? I guess
not very much.
But why are you checking the length of a container before iterating over
it? If you are writing something like this:

if len(container) != 0:
for item in container:
do_something()

then just stop it!

The same goes for

if container:
for iten in container:
do_something()

If your are writing the above you should stop it just the same.
Because you shouldn't assume containers have well-defined lengths unless
you actually care about the length, and you shouldn't assume that length
of zero implies "nothing to see here" unless you *know* that this is the
case.

I think these assumptions are equivallent with assuming it is a
container.
You should leave defining empty up to the container class itself.
Otherwise, you might be right 99 times in a hundred, but that hundredth
time will bite you.

Just as your assumption will bite you in case of numpy arrays.
A class that deliberate breaks the semantics of Python truth testing just
for the sake of breaking code really is a pathological case. Polymorphism
doesn't mean "will work with anything without exception".

I find that your idea of what a container can lack also against the
semantics of Python.
 
J

J. Clifford Dyer

Georg said:
I hope you know why this works the way it does.

Georg

Yes, I do understand why it works. I couldn't have crafted it if I
didn't, but my point is that the reason why it works is not explainable
if you believe that you are dealing with booleans. It's only
explainable if you recognize that you are actually dealing with
integers, and specifically, 1 and 0. So the something/nothing dichotomy
combined with an understanding of what the comparison operation REALLY
does (yield a 1 or a 0) helps you understand where your result came
from, while thinking in terms of true/false will mislead you.
 
G

Georg Brandl

J. Clifford Dyer said:
Yes, I do understand why it works. I couldn't have crafted it if I
didn't, but my point is that the reason why it works is not explainable
if you believe that you are dealing with booleans.

Okay, but you should have left off the second example, because it has nothing
to do with the others.
It's only
explainable if you recognize that you are actually dealing with
integers, and specifically, 1 and 0. So the something/nothing dichotomy
combined with an understanding of what the comparison operation REALLY
does (yield a 1 or a 0) helps you understand where your result came
from, while thinking in terms of true/false will mislead you.

That's true. The only sensible thing to do, if you had "real" booleans, for
1 > True, would be to raise an exception.

Georg
 

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