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 | [EMAIL PROTECTED] | Brisbane, Australia --------------------------------------------------------------- http://boredomandlaziness.skystorm.net -- http://mail.python.org/mailman/listinfo/python-list