It's pretty elementary, and people thought just describing the issue of
polymorphism and duck-typing was sufficient to explain it. Since it
apparently isn't:
Let's say you come up with some kind of custom sequence class. You want
to act like any native sequence type (list, tuple, array, string, etc.)
in all reasonable ways (length testing, iteration, indexing, etc.) so
that it can be used in place of these things in code that doesn't
require explicit types. You know, standard polymorphism and duck-typing.
So you want a test for whether your custom sequence isn't empty. To
create an "simple, explicit test" would be defined an `isntEmpty` method
that you can call, like so:
if myObject.isntEmpty():
# then do something
However, this wouldn't be polymorphic since now someone would have to
call a "simple, explicit test" that doesn't exist on all the other
sequence-like objects. Therefore, you've broken polymorphism.
The solution is to override the `__nonzero__` method so that you can use
Boolean testing, just like all the other sequence-like objects:
if myObject:
# then do the same thing
Now people who use your custom sequence type don't have to write special
code, and code written to deal with sequences using duck typing (which
is typically nearly all Python code) don't have to know anything special
about your custom sequence class.
Bzzt. "if len(x)!=0" is a simple explicit that would work for this
class and all built-in containers. (Or should--Steven D'Aprano's
objections notwithstanding, any reasonable container type should
support this invariant. From a language design standpoint, an "empty"
builtin could have been created to simplify this even more, but since
there isn't one len(x)!=0 will have to do.)
Let me reframe the question to see if we can make some headway.
The vast majority of true/false tests fit into one of the following
four categories:
1. Testing the explicit result of a boolean operation (obviously)
2. Testing whether a numeric type is nonzero.
3. Testing whether a container type is empty.
4. Testing whether you have a (non-numeric, non-container) object of
some sort, or None.
There's a few other cases, but let's start with these to keep things
simple and add other cases as necessary.
We already know that cases 2, 3, and 4 can, individually, be converted
to a simple explicit test (using x!=0, len(x)!=0, and x is not None,
respectively). As long as you know which kind of object you're
expecting, you can convert the implicit to an explicit test.
Now, you guys keep whining "But what if you don't know what kind of
object you're expecting?!!" It's a fair question, and my belief is
that, in practice, this almost never happens. Duck typing happens
between numeric types often, and between container types often, but
almost never between both numeric and container types. Their usages
are simply too different.
So I present another question to you: Give me an useful, non-trivial,
example of some code that where x could be either a numeric or
container type. That would be the first step to finding a
counterexample. (The next step would be to show that it's useful to
use "if x" in such a context.)
Once again, I'm invoking the contraint against simply using x in a
boolean context, or passing x to a function expecting a boolean
doesn't count, since in those cases x can be set to the result of the
explicit test. So none of this:
def nand(a,b): return not (a and b)
Or anything trivial like this:
def add(a,b): return a+b
Carl Banks