[Python-ideas] Adding collections.abc.Ordered

Amir Rachum amir at rachum.com
Fri Nov 6 17:11:14 EST 2015


I am suggesting the addition of a collections abstract base class called
"Ordered". Its meaning is that a collection's iteration order is part of
its API. The bulk of this mail describes a use case for this. The reason I
believe that such abstract base class is required is that there is no way
to test this behavior in a given class. An ordered collection has the exact
same interface as an unordered collection (e.g, dict and OrderedDict),
other than a _promise_ of the API that the order in which this collection
will be iterated has some sort of meaning (In OrderedDict, it is the order
in which keys were added to it.)


As examples, set, frozenset, dict and defaultdict should *not* be
considered as ordered. list, OrderedDict, deque and tuple should be
considered ordered.


Before I dive into the use case I am presenting, I would like to point out
that a similar discussion was already done on the subject (suggesting at
first that OrderedDict would abstract-subclass from Sequence) at the
following thread* - http://code.activestate.com/lists/python-ideas/29532/.
That thread was abandoned largely from a lack of a clear use case, which I
hope will be more clear in this suggestion.


I am working on package called basicstruct (
https://pypi.python.org/pypi/basicstruct). The way it works is that you
define an object that inherits from basicstruct.BasicStruct, define
__slots__ , and automatically get behaviors such as a nice __init__,
__str__, comparison functions, attribute access, dict-like access,
pickleability, etc. It is similar to namedtuple, except that it is mutable.


In the Python documentation, the following is said regarding __slots__:


   Any non-string iterable may be assigned to __slots__. Mappings may also
be used; however, in the future, special meaning may be assigned to the
values corresponding to each key.


Here's how the current__init__ method of BasicStruct looks like:


   class BasicStruct(object):

       """Class for holding struct-like objects."""


       __slots__ = ()  # should be extended by deriving classes


       def __init__(self, *args, **kwargs):

           arg_pairs = zip(self.__slots__, args)

           for key, value in chain(arg_pairs, six.iteritems(kwargs)):

               setattr(self, key, value)


           for key in self.__slots__:

               if not hasattr(self, key):

                   setattr(self, key, None)


Notice the following line:



   arg_pairs = zip(self.__slots__, args)


It assumes that __slots__ defines attributes in a certain order. So as a
use I would expect that the following code


   class MyStruct(BasicStruct):

      __slots__ = ('x', 'y')



   MyStruct(0, 1)


... will create a struct in which x is 0 and y is 1.


However, if I define __slots__ as a set, or dict, the result is undefined.


   class MyStruct(BasicStruct):

      __slots__ = {'x', 'y'}  # No order is defined here



   MyStruct(0, 1)  # Which is which?


So, In BasicStruct's __init__ method it is required to test whether
__slots__ was defined with a collection that has a meaningful order. If it
was _not_, using non-keyword arguments in __init__ should be forbidden,
since the order of __slots__ is arbitrary. Here is how I wish the code
would look like:


   class BasicStruct(object):

       """Class for holding struct-like objects."""



       __slots__ = ()  # should be extended by deriving classes



       def __init__(self, *args, **kwargs):

           ordered = isinstance(self.__slots__, Ordered)



           if args and not ordered:

               raise ValueError("Can't pass non-keyword arguments to {},
since "

                                "__slots__ was declared with an unordered "

                                "iterable.".format(self.__class__.__name__))



           arg_pairs = zip(self.__slots__, args)

           for key, value in chain(arg_pairs, six.iteritems(kwargs)):

               setattr(self, key, value)



           for key in self.__slots__:

               if not hasattr(self, key):

                   setattr(self, key, None)


Thanks,

Amir Rachum




* The author of the original thread is Ram Rachum. To avoid some confusion
- yes, we are related - Ram is my big brother. It's just so happens that I
was looking around for this issue and found that he did so in the past as
well.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20151107/0a55fd7f/attachment-0001.html>


More information about the Python-ideas mailing list