[Python-3000] callable()

Alex Martelli aleaxit at gmail.com
Mon Jul 17 19:30:25 CEST 2006


On 7/17/06, Andrew Koenig <ark at acm.org> wrote:
> I note in PEP 3000 the proposal to remove callable(), with the comment "just
> call the object and catch the exception."
>
> I think that's a bad idea, because it takes away the ability to separate the
> callability test from the first call.  As a simple example, suppose you're
> writing a function that you expect to be given a function as one of its
> arguments:
>
>         def foo(bar, fun):
>                 assert callable(fun)
>                 # ...
>
> It might be that foo doesn't actually call fun until much later.
> Nevertheless, from a testing viewpoint, it would be better to detect the
> error immediately of passing something that can't be called.
>
> If you didn't have callable, how would you write this example?

I might equivalently hasattr(type(fun), '__call__') [[net of oldstyle
classes, which won't be around in py3k anyway:-)]] and it would be
just as feeble -- essentially because when the time comes to CALL fun,
it will be called with specific signature[s], not "in a void".

IOW, callable as it stands is feeble because it tries to check for
some too-wide category of types -- all types whose instances may be
called in SOME way or other, while in fact the call[s] when they come
will use *specific* signatures.  This general kind of problem is of
course familiar to users of compile-time-checked languages, who may be
able e.g. to specify "x is an int" when what they really need is to
assert "x is an even positive int" and the like -- but being a
familiar evil does not make it any more sensible to me.

If 'callable' is to stay, then in order to pull its weight it needs to
grow to provide a way to check "callable with the following signature"
-- at the very least in terms of numbers and names of arguments,
though (if py3k does gain some syntax for specifying argument types)
it might do more.  E.g., just "callable(fun)" should mean "fun appears
to be callable without arguments, e.g. I believe that doing 'fun()'
will not result in a TypeError from the call operation itself", while
"callable(fun, int, zap=list[int])" should mean "fun appears to be
callable with one positional argument that's an int and an argument
named zap that is a list of ints", i.e. the apparently-acceptable call
is of some form such as "fun(23, [4, 5])" [[presumably using object as
a placeholder for unchecked-types]] -- so that for example a

def fun(zip, zap):

would be OK (the name of the 2nd arg is correct, and fun doesn't check
argument types so passing int and list of int looks like it should be
OK), as would, say,

def fun(zip: float, **k):

assuming int->float is intrinsically accepted, since **k accepts
arbitrary named arguments, but for example

def fun(zip, zop):

would not pass the check, since the misnamed 2nd argument would
produce a type error at calltime; etc, etc.

The def statement (or __init__/__new__ for callability of classes, or
__call__ for callability of instances) should record enough
information about acceptable signatures to make this enriched
"callable" conceptually feasible, as long as we accept some false
positives (we probably should be ready to accept them, since it's
unfeasible to say exactly what further constraints fun's code body
could place on acceptable arguments, sigh).  Perhaps the best syntax
for the functionality in question might be different: e.g.,
callable(<list of arguments, which are types and may be named>) could
be some kind of type or interface, so that one would check with
    isinstance(fun, callable())
to check if fun is callable without arguments, etc, etc; this should
have the advantage of playing well with functions specifying
constraints on their arguments, so that e.g.

def foo(bar, fun: callable()):

would be the vastly-preferred way to express the code snippet you give
(assuming that you want fun to be "callable _without arguments_"
specifically, of course -- but then, I've already argued that being
"callable in SOME ways that nobody can guess" is not a sensible
constraint to check for, hm?-).  Putting typechecks in foo's signature
records them for posterity and eases somebody else's task of checking
if *foo* itself is callable in the way they'd like to call it...


Alex


More information about the Python-3000 mailing list