[Python-3000] callable()
Talin
talin at acm.org
Wed Jul 19 09:42:38 CEST 2006
Greg Ewing wrote:
> Guido van Rossum wrote:
>
>
>>But I'm not proposing to use hasattr(obj, '__call__'). I'm basically
>>saying that the callable test has very little to do with whether the
>>later call will succeed, because we have no way to test the signature.
>
>
> I don't think that's needed for the sort of things people
> want callable() for. I think they want it for the purpose
> of implementing type-dependent APIs -- similar in spirit
> to doing different things depending on whether you're
> passed a sequence or not, etc.
I think this is exactly correct. Those who want to retain callable()
[including myself] aren't looking for an ironclad guarantee that calling
a given object will succeed - they merely want to check to see if the
object is "of callable type", in other words, is it the roughly sort of
object that one would expect to be able to call.
I think part of the reason that this debate has gone on so long is that
it's really a debate about the nature of Python as a scripting language.
Every so-called 'scripting' language maintains a balance between
strictness and forgiveness. On the one extreme, there are languages such
as Lua, where accessing an undefined variable is silently ignored, or
Javascript, where exceptions that would otherwise be fatal errors are
merely logged as warnings. On the other hand, there is a long tail of
increasingly bondage-and-dominance-oriented languages with ever more
strict checking of the rules.
As I am sure that all of us have discovered, the 'looser' language rules
can greatly increase the speed of rapid prototyping. My own experience
is that I can code Java about twice as fast as C++, and Python about
twice as fast as Java, for equivalent functionality.
However, these looser rules have their own costs, which I won't go into
detail about. Certainly the strict languages create lots more
opportunities for compile-time optimization. As to whether the strict
languages are more reliable overall, that's an open question - while its
likely that the the programs written in such languages are more provably
correct, the very strictness of the language's own self-checking can
itself become a source of, um, programs that don't work when they should.
There's a common architectural pattern seen in both the Python standard
library as well as many user-written applications, which I will call the
"rough protocol" pattern (you might also think of it as "duck
protocols"). The pattern is simple: Objects which conform to a given
interface need not conform to it in theoretical exactitude, as would be
required in a language such as C# or C++. Instead, objects only need
conform to an interface so far as it is practical and convenient to do so.
As an example, I can create a "file-like" object, without having to
support all of the various little methods and subtle behaviors of "file".
The reason these rough protocols are of such benefit is that you save a
great deal of time - you can avoid having to track down and implement
all the little subtle fiddly bits of the given interface that would
otherwise be needed to make the compiler happy, but which you don't
actually plan to use.
One of the types of rough protocols that we often see is one in which an
object is passed as an argument to a function, and the function attempts
to classify the object according to some simple criteria into one of
several recognized types, and then take action based on that categorization.
You see this all the time with things like "if X is a sequence, then do
this with it, otherwise do that".
Now, this violates all kinds of theoretical considerations such as
"well, what if X only vaguely resembles a sequence', or 'what if X looks
like a sequence, but really isn't'. One can take the formal definition
of 'sequence' and generate an endless series of mutations and
permutations of that definition which may or may not work for a given
function (or even make sense for that matter).
The practical answer to all of this is that such contrivances hardly
ever occur in real code. As programmers, we have common-sense
expectations of what is a sequence, what is a map, what is a function,
and so on. Those are the primitive concepts with which we assemble our
mental designs before putting them down on disk. As soon as a class
starts to stray too far outside the envelope of those expectations, we
tend to call it something besides 'sequence' or 'map' or whatever.
Rough protocols don't need to know exactly the type of object or what
its exact method signatures are. It only needs to know enough about the
object to decide what common-sense category to place it in.
Rough protocols *aren't* guaranteed to be able to inspect the object
thoroughly enough to know whether the category test is valid - it
assumes that the user is smart enough to only pass in objects for which
the classification test makes sense. If you happen to pass in an object
that doesn't fit in any category, or is categorized wrongly, then that's
a bug in your code and you should go fix it.
So for example, when the documentation for 'sort' says that the 'key'
argument can be a function that returns the key value, that doesn't mean
that you can pass in an arbitrary function such as 'file.open' and
expect it to magically work.
So, the point of all this is that while callable() isn't at all useful
for the kind of formal proof-that-it-will-not-blow-up-horribly kind of
inspection, it is very useful as a rough category test. In fact that's
pretty much all I have ever used it for, and I am sure that many others
would agree.
In fact, I would go so far as to say that the kind of "exact parameter
signature" test being advocated would be *less* useful, simply because
it would take more time to code to get the same effect.
-- Talin
More information about the Python-3000
mailing list