namespaces module (a.k.a. bunch, struct, generic object, etc.) PEP

Nick Coghlan ncoghlan at iinet.net.au
Fri Feb 11 23:36:51 EST 2005


Steven Bethard wrote:
>>> Should namespace chaining be supported?  One suggestion would add a
>>> NamespaceChain object to the module::
>>
>>
>> This does have the advantage of keeping the basic namespace simple. 
>> However, it may also be worth having native chaining support in 
>> Namespace:
> 
> 
> I think I prefer the separate NamespaceChain object because it allows 
> you to chain namespaces other than just Namespace objects -- any object 
> that supports getattr is okay.  If chaining is builtin, all namespaces 
> (except the last one?) have to be Namespace objects...

I like NamespaceChain too - I was simply thinking it might be good to have a
recursive chaining method associated with actual Namespace objects as well.

However, now that I look more closely at NamespaceChain, it makes more sense to
me to explicitly make the 'head' of the chain a namespace view in its own right.
This should also make the local binding, chained lookup behaviour fairly obvious
(since it will work just as it does for any class with __getattr__ defined, but
not __setattr__ or __delattr__).


That is, something like:

     class NamespaceChain(NamespaceView):
         def __init__(self, head, *args):
             NamespaceView.__init__(self, head)
             self.__namespaces__ = args

         def __getattr__(self, name):
             """Return the first such attribute found in the object list

             This is only invoked for attributes not found in the head
             namespace.
             """
             for obj in self.__namespaces__:
                 try:
                     return getattr(obj, name)
                 except AttributeError:
                     pass
             raise AttributeError(name)


Python gives us the local set and local del for free.

The 'nested namespaces' approach that prompted my original suggestion can then
be spelt by using:

   parent = Namespace()
   child1 = NamespaceChain({}, parent)
   child2 = NamespaceChain({}, child1)

There *is* a problem with using __getattr__ though - any attribute in the 
chained namespaces that is shadowed by a class attribute (like 'update') will be 
picked up from the class, not from the chained namespaces. So we do need to use 
__getattribute__ to change that lookup order. However, given the amount of grief 
the default lookup behaviour causes, I think the place to do that is in 
Namespace itself:

     class Namespace(object):
         # otherwise unchanged
         def __getattribute__(self, name):
             """Namespaces do NOT default to looking in their class dict
             for non-magic attributes
             """
             getattribute = super(Namespace, self).__getattribute__
             try:
                 return getattribute("__dict__")[name]
             except KeyError:
                 if name.startswith('__') and name.endswith('__'):
                     # Employ the default lookup system for magic names
                     return getattribute(name)
                 else:
                     # Skip the default lookup system for normal names
                     if hasattr(self, "__getattr__"):
                         return getattribute("__getattr__")(name)
                     else:
                         raise AttributeError(name)

The above is a pretty simple approach - it completely bypasses the descriptor 
machinery for non-magic names. This means that the instance namespace does NOT 
get polluted by any non-magic names in the class dictionary, but magic names can 
be accessed normally. And subclasses can add their own methods, and this feature 
will continue to 'just work'. For example:

Py> ns = namespaces.Namespace()
Py> ns.update
Traceback (most recent call last):
   File "<stdin>", line 1, in ?
   File "namespaces.py", line 30, in __getattribute__
     raise AttributeError(name)
AttributeError: update
Py> type(ns).update
<unbound method Namespace.update>
Py> ns.__init__
<bound method Namespace.__init__ of Namespace()>

(side note: I don't think I have ever written a __getattribute__ method without 
introducing an infinite recursion on the first attempt. . .)

Anyway, it is probably worth digging into the descriptor machinery a bit further 
in order to design a lookup scheme that is most appropriate for namespaces, but 
the above example has convinced me that object.__getattribute__ is NOT it :)

Cheers,
Nick.

-- 
Nick Coghlan   |   ncoghlan at email.com   |   Brisbane, Australia
---------------------------------------------------------------
             http://boredomandlaziness.skystorm.net




More information about the Python-list mailing list