[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