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

Nick Coghlan ncoghlan at iinet.net.au
Sat Feb 12 02:54:54 EST 2005


Steven Bethard wrote:
 > Hmmm...  This does seem sensible.  And sidesteps the issue about
 > suggesting that subclasses use Namespace.update instead of
 > namespaceinstance.update -- the latter just won't work!  (This is a Good
 > Thing, IMHO.)

Yeah, I thought so too. It also crystallised for me that the real problem was 
that we were trying to use the attribute namespace for something different from 
what it is usually used for, and so Python's normal lookup sequence was a 
hindrance rather than a help.

I guess Guido knew what he was doing when he provided the __getattribute__ hook ;)

One other point is that dealing correctly with meta-issues like this greatly 
boosts the benefits from having this module in the standard library. I would 
expect most of the 'roll-your-own' solutions that are out there deal badly with 
this issue. "Inherit from namespaces.Namespace" is a simpler instruction than 
"figure out how to write an appropriate __getattribute__ method".

>> 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 :)
> 
> 
> Yeah, I'm going to look into this a bit more too, but let me know if you 
> have any more insights in this area.

My gut feel is that we want to get rid of all the decriptor behaviour for normal 
names, but keep it for special names. After all, if people actually wanted that 
machinery for normal attributes, they'd be using a normal class rather than a 
namespace.

I'm also interested in this because I'd like to let the 'Record' class (see 
below) be able to store anything in the defaults, including descriptors and have 
things 'just work'.

The __getattribute__ I posted comes close to handling that, but we need 
__setattr__ and __delattr__ as well in order to deal fully with the issue, since 
override descriptors like property() can affect those operations. Fortunately, 
they should be relatively trivial:

     # Module helper function
     def _isspecial(name):
         return name.startswith("__") and name.endswith("__")

     # And in Namespace
     def __getattribute__(self, name):
         """Namespaces only use __dict__ and __getattr__
         for non-magic attribute names.
         Class attributes and descriptors like property() are ignored
         """
         getattribute = super(Namespace, self).__getattribute__
         try:
             return getattribute("__dict__")[name]
         except KeyError:
             if _isspecial(name)
                 # 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('%s instance has no attribute %s'
                                          % (type(self).__name__, name))

     def __setattr__(self, name, val):
         """Namespaces only use __dict__ for non-magic attribute names.
         Descriptors like property() are ignored"""
         if _isspecial(name):
             super(Namespace, self).__setattr__(name, val)
         else:
             self.__dict__[name] = val

     def __delattr__(self, name):
         """Namespaces only use __dict__ for non-magic attribute names.
         Descriptors like property() are ignored"""
         if _isspecial(name):
             super(Namespace, self).__delattr__(name)
         else:
             del self.__dict__[name]

In action:

Py> def get(self): print "Getting"
...
Py> def set(self, val): print "Setting"
...
Py> def delete(self): print "Deleting"
...
Py> prop = property(get, set, delete)
Py> class C(object):
...   x = prop
...
Py> c = C()
Py> c.x
Getting
Py> c.x = 1
Setting
Py> del c.x
Deleting
Py> class NS(namespaces.Namespace):
...   x = prop
...
Py> ns = NS()
Py> ns.x
Traceback (most recent call last):
   File "<stdin>", line 1, in ?
   File "namespaces.py", line 40, in __getattribute__
     raise AttributeError('%s instance has no attribute %s'
AttributeError: NS instance has no attribute x
Py> ns.x = 1
Py> del ns.x
Py> ns.__x__
Getting
Py> ns.__x__ = 1
Setting
Py> del ns.__x__
Deleting

I've attached my latest local version of namespaces.py (based on the recent PEP 
draft) which includes all of the above. Some other highlights are:

NamespaceView supports Namespace instances in the constructor

NamespaceChain inherits from NamespaceView (as per my last message about that)

LockedView is a new class that supports 'locking' a namespace, so you can only 
modify existing names, and cannot add or remove them

NamespaceChain and LockedView are the main reason I modified NamespaceView to 
directly support namespaces - so that subclassed could easily support using either.

Record inherits from LockedView and is designed to make it easy to define and 
create fully specified "data container" objects.

Cheers,
Nick.

-- 
Nick Coghlan   |   ncoghlan at email.com   |   Brisbane, Australia
---------------------------------------------------------------
             http://boredomandlaziness.skystorm.net
-------------- next part --------------
An embedded and charset-unspecified text was scrubbed...
Name: namespaces.py
URL: <http://mail.python.org/pipermail/python-list/attachments/20050212/63060d2f/attachment.ksh>


More information about the Python-list mailing list