[Zope-Coders] new getattr behaviour in Python 2.2b1 breaks traversal code

Steve Alexander steve@cat-box.net
Sun, 11 Nov 2001 13:00:17 +0000


I've just been caught out by a change between Python 2.1 and Python 2.2b1.

This is a bad change which will mean changing various parts of Zope and 
Zope products, if it remains in Python 2.2 final.


Here's some information about the change.

http://sourceforge.net/tracker/index.php?func=browse&group_id=5470&atid=105470&set=&offset=100

In summary, the getattr builtin will only return its default value if a 
__getattr__ method raises an AttributeError.

This is a problem because various __getattr__ methods in Zope are the 
same as __getitem__ methods, and return KeyErrors.


Basically, this code works as expected:

Python 2.2b1 (#1, Oct 28 2001, 17:32:42)
[GCC 2.96 20000731 (Red Hat Linux 7.1 2.96-85)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
 >>> class Foo:
...   def __getitem__(self, key):
...     print "__getitem__ %s" % (key,)
...     raise AttributeError, key
...   __getattr__=__getitem__
 >>>
 >>> getattr(f,'asd','default value')
__getitem__ asd
'default value'

However, this code does not work as expected:

 >>> class Foo:
...   def __getitem__(self, key):
...     print "__getitem__ %s" % (key,)
...     raise KeyError, key
...   __getattr__=__getitem__
...
 >>>
 >>> f=Foo()
 >>> getattr(f, 'asd', 'default val')
__getitem__ asd
Traceback (most recent call last):
   File "<stdin>", line 1, in ?
   File "<stdin>", line 4, in __getitem__
KeyError: asd


There is code in Zope which uses the latter idiom of using the same 
method for __getattr__ and __getitem__. An immediate effect of this is 
that TTW PageTemplates don't work with Zope and Python 2.2b1.

Line 338 of PageTemplates/Expressions.py

   t=get(object, '__bobo_traverse__', N)

At this point, get is a local alias for getattr. N is None.

Unfortunately, an HTTPRequest object raises a KeyError rather than an 
AttributeError, as it uses the latter idiom above.

See ZPublisher/HTTPRequest.py, around line 898.


If this change must remain in Python 2.2, Zope could be fixed by 
removing all occurances of __getattr__=__getitem__ (or vice versa) and 
writing them as separate methods. Of course, this will have to be done 
in third party products as well.

Otherwise, the traversal code could be hardened against KeyErrors raise 
by getattr.

--
Steve Alexander
Software Engineer
Cat-Box limited