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

Nick Coghlan ncoghlan at iinet.net.au
Fri Feb 11 07:19:01 EST 2005


Steven Bethard wrote:
> In the "empty classes as c structs?" thread, we've been talking in some 
> detail about my proposed "generic objects" PEP.  Based on a number of 
> suggestions, I'm thinking more and more that instead of a single 
> collections type, I should be proposing a new "namespaces" module 
> instead.  Some of my reasons:
> 
> (1) Namespace is feeling less and less like a collection to me.  Even 
> though it's still intended as a data-only structure, the use cases seem 
> pretty distinct from other collections.

Particularly since the collections module is billed as a location for 
purpose-specific optimised data structures where some general-purpose features 
have been dropped in order to make other aspects faster.

All the ideas here relate to conveniently manipulating namespaces, and are 
independent enough to make sense as separate classes. I think a new module is a 
good call.

It also allows a Python implementation :)

>     >>> ns = Namespace(eggs=1)
>     >>> Namespace.update(ns, [('spam', 2)], ham=3)
>     >>> ns
>     Namespace(eggs=1, ham=3, spam=2)
> 
> Note that update should be used through the class, not through the
> instances, to avoid the confusion that might arise if an 'update'
> attribute added to a Namespace instance hid the update method.

I'd like to see the PEP text itself encourage the more inheritance friendly 
style used in the __init__ method:
   type(ns).update(ns, [('spam', 2)], ham=3)

Accessing the class directly means that you may get incorrect behaviour if ns is 
actually an instance of a subclass of Namespace that overrides update.

It may also be worth mentioning that the standard library applies this technique 
in order to reliably access the magic methods of an instance's type, rather than 
the versions shadowed in the instance (this is important when trying to pickle 
or copy instances of 'type' for example).

> Note that support for the various mapping methods, e.g.
> __(get|set|del)item__, __len__, __iter__, __contains__, items, keys,
> values, etc. was intentionally omitted as these methods did not seem
> to be necessary for the core uses of an attribute-value mapping.  If
> such methods are truly necessary for a given use case, this may
> suggest that a dict object is a more appropriate type for that use.

The 'vars' builtin also makes it trivial to use dictionary style operations to 
manipulate the contents of a Namespace.

>     class Namespace(object):
>         def __eq__(self, other):
>             """x.__eq__(y) <==> x == y
> 
>             Two Namespace objects are considered equal if they have the
>             same attributes and the same values for each of those
>             attributes.
>             """
>             return (other.__class__ == self.__class__ and
>                     self.__dict__ == other.__dict__)

Hmm, the exact class check strikes me as being rather strict. Although I guess 
it makes sense, as the loose check is easily spelt:
   if vars(ns1) == vars(ns2): pass

*shrug* I'll let other people argue about this one :)

>         def update(*args, **kwargs):
>             """Namespace.update(ns, [ns|dict|seq,] **kwargs) -> None

Same comment as above about encouraging the inheritance friendly style.

> Open Issues
> ===========
> What should the types be named?  Some suggestions include 'Bunch',
> 'Record', 'Struct' and 'Namespace'.
> Where should the types be placed?  The current suggestion is a new
> "namespaces" module.

If there aren't any objections to the current answers to these questions in this 
thread, you may finally get to move them to a 'Resolved Issues' section :)

> 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:

     class Namespace(object):
         def __init__(*args, **kwds):
             if not 1 <= len(args) <= 3:
                 raise TypeError('expected 1 to 3 non-keyword arguments,'
                                 'got %i' % len(args))
             self = args[0]
             # Set __fallback__ first, so keyword arg can override
             if len(args) > 2:
                 self.__fallback__ = args[2]
             else:
                 self.__fallback__ = None
             # Update __dict__. OK to use normal method calls, since dict hasn't
             # been updated yet
             if len(args) > 1 and args[1] is not None:
                 self.update(args[1], **kwds)
             elif kwds:
                 self.update(**kwds)

         def __getattr__(self, attr):
             if self.__fallback__ is not None:
                 return getattr(self.__fallback__, attr)
             raise AttributeError("No attribute named " + attr)

         # Otherwise unchanged



>     class NamespaceChain(object):
>         """NamespaceChain(*objects) -> new attribute lookup chain
> 
>         The new NamespaceChain object's attributes are defined by the
>         attributes of the provided objects.  When an attribute is
>         requested, the sequence is searched sequentially for an object
>         with such an attribute.  The first such attribute found is
>         returned, or an AttributeError is raised if none is found.
> 
>         Note that this chaining is only provided for getattr and delattr
>         operations -- setattr operations must be applied explicitly to
>         the appropriate objects.

Hmm, I'm not so sure about this. I think that the right model is the way that a 
class instance is currently chained with its class.

That is, assume we have the following:
   c = cls()
   ns = Namespace(vars(c), vars(cls)) # Using modified NS above
   nc = NamespaceChain(Namespace(vars(c)), Namespace(vars(cls)))


I would expect modification of attributes on ns or nc to behave similarly to 
modification of attributes on c - attribute retrieval follows the chain, but 
attribute modification (set/del) always operates on the first namespace in the 
chain.

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



More information about the Python-list mailing list