(test) ? a:b

Steven D'Aprano steve+comp.lang.python at pearwood.info
Sun Oct 26 05:15:06 CET 2014


Ben Finney wrote:

> Steven D'Aprano <steve+comp.lang.python at pearwood.info> writes:
> 
>> I suspect that Guido and the core developers disagree with you, since
>> they had the opportunity to fix that in Python 3 and didn't.
> 
> That doesn't follow; there are numerous warts in Python 2 that were not 
> fixed in Python 3. As I understand it, the preservation of bool–int
> equality has more to do with preserving backward compatibility.

On reviewing PEP 285, I think it is safe to say that Guido *explicitly*
wants bools to be ints, not just for backwards compatibility:

    4) Should we strive to eliminate non-Boolean operations on bools
       in the future, through suitable warnings, so that for example
       True+1 would eventually (in Python 3000) be illegal?

    => No.

       There's a small but vocal minority that would prefer to see
       "textbook" bools that don't support arithmetic operations at
       all, but most reviewers agree with me that bools should always
       allow arithmetic operations.

http://legacy.python.org/dev/peps/pep-0285/


> I agree with the decision, because this isn't an issue which often leads
> to *incorrect* code. But I maintain that it's an unfortunate and
> needlessly confusing wart of the language.

That puts you in the small but vocal minority :-)


>> One example of where treating True and False as 1 and 0 is useful is
>> that it makes counting operations very simple, e.g. counting the
>> number of blank lines:
>>
>> sum(not line.strip() for line in lines)
>> sum(1 if not line.strip() else 0 for line in lines)
>> sum({'': 0}.get(line.strip(), 1) for line in lines)
> 
> These all look ludicrous and explain nothing about intent, to my
> reading.
[...]
> I'm left wondering why
> the coder chose something so obfuscatory to their intent.

"Ludicrous" and "obfuscatory". They're awfully strong words.

Do you really not see the connection between counting and summing? If so,
that would put you in a truly small minority. The connection between adding
and counting has been known to Western mathematics since at least the
ancient Greeks, if not even earlier, and we teach it to school children. If
you have three apples, and I have two apples, then in total we have (count
the apples: one two three, four five) five apples. Pre-schoolers learn
this. I'm sure it's not too difficult for programmers.


> This is short and clear and needs no leaking of the underlying bool
> implementation::
> 
>     len(True for line in lines if line.strip())

I see that in a later email you discovered your unfortunate bug in this, and
corrected it to this:

    len([True for line in lines if line.strip()])

Alas, you missed the bigger bug: I'm counting *blank lines*, not non-blank
lines. You need a `not` in there.

You mentioned intent before. I wonder what your intention is, to needlessly
build a potentially gigantic list of identical values, only to then throw
the list away. Could be worse; you might have written

    [True for line in lines if not line.strip()].count(True)

Since the list items exist only to be counted, the actual item used makes no
difference. You could use any value at all, or even a different value each
time:

    len([random.random() for line in lines if not line.strip()])

What is your reason for the choice of True as the tally marker? It doesn't
clarify your intent, or make it more obvious that you're counting the
number of non-empty lines. If you wanted to suggest a tally:

    len(['/' for line in lines if not line.strip()])

at least looks like a tally: //// makes four. Using a bool doesn't make this
any clearer, and you don't even have the excuse that it's efficient,
because it isn't.

Let's get rid of the list comp and write a straightforward translation:

temp = []
for line in lines:
    if not line.strip():
        temp.append(True)
result = len(temp)
del temp


I don't consider that sensible code at all. Why waste time building the
temporary list when all we want is a counter?

result = 0
for line in lines:
    if not line.strip():
        result += 1


which can be simplified to:

result = 0
for line in lines:
    result += not line.strip()


I agree with you that this breaks the abstraction "bools are abstract
enumerations, not ints", but that abstraction is not part of Python. If I
objected to code like this:

    types = [int, str, list, dict]
    for t in types:
        do_something_with(t)

on the basis that we shouldn't treat types as values, because that breaks
the abstraction that "types are not values, they're a different kind of
thing known only to the compiler" (as in Java), I expect that you would
have no hesitation in telling me that my objection was invalid. Making
types be non-values may, or may not, be a reasonable programming model, but
it's not Python's programming model. In Python, types are themselves
objects and first-class values. Objecting to Python code because it
violates a norm from some other language is, frankly, silly.

The abstraction "bools are abstract enumerations different from ints" may be
a reasonable abstraction, but it's not part of Python's programming model

[...]
>> which I consider far less obvious than the straightforward summation
>> of True values.
> 
> Why sum them at all? You aren't interested in them as numbers, you're
> just asking how many objects meet a criterion. That calls not for ‘sum’,
> but ‘len’. There's no need to rely on an underlying ‘int’ operation just
> to count objects.

A length (in the sense of the discrete count of the number of items) is
fundamentally a sum of ones. That is what counting *is*.


> The fact that summing them gives the same answer as simply asking how
> many there are, demonstrates that nothing is gained by peeking into the
> implementation. The example I give above works exactly as well with no
> ‘int’ underlying the ‘bool’ type at all.

And if you were programming in a language where bools were not a sub-type of
int, perhaps your code might be preferred. But even then, why build an
unnecessarily large list, only to throw it away unused apart from counting
the items? You can just count the items as they appear. Even in some other
hypothetical language with abstract bools, say, Pascal, I'd write it as:

    sum(1 for line in lines if not line.strip())

rather than build a full list that I don't care about.


-- 
Steven




More information about the Python-list mailing list