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