Some anonymous Planet Python poster (at least I couldn't find the author's
name on the blog) Christian Wyglendowski asks
about a surprising difference between old-style and new-style classes.
Since the comments on their blog are closed (which you find out only after
pressing Submit), I'll answer here.
The question, slightly paraphrased: given a class
class LameContainerOld:
def __init__(self):
self._items = {'bar':'test'}
def __getitem__(self, name):
return self._items[name]
def __getattr__(self, attr):
return getattr(self._items, attr)
why does the 'in' operator work
>>> container = LameContainerOld()
>>> 'foo' in container
False
>>> 'bar' in container
True
when the equivalent new-style class raises a KeyError: 0 exception? Also, why
does __getattr__ appear to be called to get the bound __getitem__ method of the
dict?
>>> container.__getitem__
<bound method LameContainerNew.__getitem__ of {'bar': 'test'}>
What actually happens here is that LameOldContainer.__getattr__ gets called
for special methods such as __contains__ and __repr__. This is why (1) the
'in' check works, and (2) it appears, at first glance, that you get the wrong
__getitem__ bound method. If you pay close attention to the output, you'll
see that it's the __getitem__ of LameOldContainer; it's just that
repr(LameOldContainer()) gets proxied through to the dict.__repr__ when you
don't expect it:
>>> container
{'bar': 'test'}
Special methods never go through __getattr__ for new-style classes,
therefore neither __contains__ nor __repr__ are proxied if you make the
container inherit object. If there's no __contains__ method, Python falls back
to the sequence protocol and starts calling __getitem__ for numbers 0 through
infinity, or until it gets an IndexError exception.