[ZODB-Dev] Persistent-derived class instances always callable

Tim Peters tim at zope.com
Sat Oct 18 00:56:43 EDT 2003


[John Belmonte]
> I have a function that generates XML elements from a list of (Name,
> Content) tuples.  Normally the element content is set to str(Content),
> but if Content is a list I recurse, and if it is callable I call it
> and use the result.

[Tim]
>> As is, ExtensionClass instances truly are callable as far as Python
>> is concerned (they have a tp_call slot, which Python calls), and
>> trying to convince Guido that "well, ya, they're callable, but not
>> in a sense that's always useful to this particular app, so Python
>> should grow some other idea of 'callable' that does make sense to
>> this app" is going to be a hard sell.

[John]
> It sounds like callable() as it is is useless,

I don't think it's useless:  AFAICT, it does exactly what it should do in
core Python.  ExtensionClass isn't part of Python, it's a large mass of
complex C extension code in Zope, and tried to do an extremely difficult
thing at the time (make classes in C -- before Python 2.2, classes and types
were disjoint concepts in Python, and the core supported only Python-defined
classes and only C-defined types; ExtensionClass had to muck around in
dozens of dark internal corners to try to make classes defined in C, and
some of the corners still don't fit perfectly; 2.2 went a long way toward
healing the type/class split).

Whether callable "should be" used is a different question.

> which is probably why you've never used it.

It's more that explicit type-sniffing is *generally* a sign of dubious
design when using an OO language.  If, in your example, the second element
of tuples had to derive from a Content class, then subclasses could override
a (say) get_content() method to do whatever they needed to do (return a
string, recurse, call, fetch new code from a URL, whatever), and the
invoking code wouldn't need any type-sniffing.

> I suppose in my case I could just try calling every object and
> catch the TypeError.

I think you actually got AttributeError in your case, but, regardless,

try:
    Content()
except WhateverError:
    pass

is also going to hide unexpected WhateverErrors that occur in a flawed
implementation of Content, and in whatever it may call in turn.

If you need to do type-sniffing, it's better to reverse-engineer the quirks
introduced by ExtensionClass and build a type-sniffer catering to them.
Maybe this would work (I really don't know for sure!):

def callable(thing):
    import ExtensionClass, __builtin__
    if hasattr(thing, '__basicnew__'):
        # __basicnew__ is a sign of ExtensionClass; nothing in the
        # Python core, or any other known extension module, has it.
        return (isinstance(thing, ExtensionClass.ExtensionClass) or
                hasattr(thing, "__call__"))
    else:
        # Under the theory that only ExtensionClass sets tp_call
        # even when instances aren't intended to be callable.
        return __builtin__.callable(thing)

Then, e.g.,

>>> class C(Persistent): pass
>>> callable(C)
True
>>> callable(C())
False

>>> class C: pass  # old-style class
>>> callable(C)
True
>>> callable(C())
False

>>> class C(object): pass  # new-style class
>>> callable(C)
True
>>> callable(C())
False

>>> from BTrees.OOBTree import OOBTree # another kind of extension class
>>> callable(OOBTree)
True
>>> callable(OOBTree())
False

>>> import math
>>> callable(math)
False
>>> callable(math.sin)
True
>>>

This relies on sniffing for __basicnew__, so has its own danger of relying
on murky internals across releases.




More information about the ZODB-Dev mailing list