Lichen

lib/__builtins__/sequence.py

351:c2bdc5b1a125
2016-12-08 Paul Boddie Ensure that base classes provide the attributes they use. Added tests demonstrating the use of attributes in base classes that are not necessarily defined in those classes or in some subclasses.
     1 #!/usr/bin/env python     2      3 """     4 Sequence operations.     5      6 Copyright (C) 2015, 2016 Paul Boddie <paul@boddie.org.uk>     7      8 This program is free software; you can redistribute it and/or modify it under     9 the terms of the GNU General Public License as published by the Free Software    10 Foundation; either version 3 of the License, or (at your option) any later    11 version.    12     13 This program is distributed in the hope that it will be useful, but WITHOUT    14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS    15 FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more    16 details.    17     18 You should have received a copy of the GNU General Public License along with    19 this program.  If not, see <http://www.gnu.org/licenses/>.    20 """    21     22 from native import _isinstance    23     24 class itemaccess:    25     26     "An abstract class providing item access."    27     28     def _check_index(self, index):    29     30         """    31         Check the given absolute 'index', raising an IndexError if out of    32         bounds.    33         """    34     35         if index < 0 or index >= len(self):    36             raise IndexError(index)    37     38     def __getitem__(self, index):    39     40         "Return the item or slice specified by 'index'."    41     42         # Normalise any integer indexes, converting negative indexes to positive    43         # ones.    44     45         if _isinstance(index, int):    46             index = _get_absolute_index(index, self.__len__())    47             return self.__get_single_item__(index)    48     49         # Handle slices separately.    50     51         elif _isinstance(index, slice):    52             return self.__getslice__(index.start, index.end, index.step)    53     54         # No other kinds of objects are supported as indexes.    55     56         else:    57             raise TypeError()    58     59     def __setitem__(self, index, value):    60     61         "Set at 'index' the given 'value'."    62     63         # Normalise any integer indexes, converting negative indexes to positive    64         # ones.    65     66         if _isinstance(index, int):    67             index = _get_absolute_index(index, self.__len__())    68             return self.__set_single_item__(index, value)    69     70         # Handle slices separately.    71     72         elif _isinstance(index, slice):    73             return self.__setslice__(index.start, index.end, value)    74     75         # No other kinds of objects are supported as indexes.    76     77         else:    78             raise TypeError()    79     80     def __getslice__(self, start, end=None, step=1):    81     82         """    83         Return a slice of the sequence starting from the 'start' index, ending    84         before the optional 'end' (or at the end of the sequence), and providing    85         items at the frequency given by 'step' (with a default step of 1).    86         """    87     88         if step == 0:    89             raise ValueError(step)    90     91         length = self.__len__()    92     93         # Handle a null start as the first position, otherwise normalising any    94         # start index.    95     96         if start is None:    97             start = 0    98         else:    99             start = _get_absolute_index(start, length)   100    101         # Handle a null end as the first position after the end of the sequence,   102         # otherwise normalising any end index.   103    104         if end is None:   105             end = length   106         else:   107             end = _get_absolute_index(end, length)   108    109         result = []   110    111         while step > 0 and start < end or step < 0 and start > end:   112             result.append(self.__get_single_item__(start))   113             start += step   114    115         return result   116    117     # Methods implemented by subclasses.   118    119     def __setslice__(self, start, end, value):   120    121         "Method to be overridden by subclasses."   122    123         pass   124    125     def __get_single_item__(self, index):   126    127         "Method to be overridden by subclasses."   128    129         return None   130    131     def __set_single_item__(self, index, value):   132    133         "Method to be overridden by subclasses."   134    135         pass   136    137     def __len__(self):   138    139         "Method to be overridden by subclasses."   140    141         return 0   142    143 class sequence(itemaccess):   144    145     "A common base class for sequence types."   146    147     def _str(self, opening, closing):   148    149         "Serialise this object with the given 'opening' and 'closing' strings."   150    151         b = buffer()   152         i = 0   153         l = self.__len__()   154         first = True   155    156         b.append(opening)   157         while i < l:   158             if first:   159                 first = False   160             else:   161                 b.append(", ")   162             b.append(repr(self.__get_single_item__(i)))   163             i += 1   164         b.append(closing)   165    166         return str(b)   167    168     def __contains__(self, value):   169    170         "Return whether the list contains 'value'."   171    172         # Perform a linear search of the sequence contents.   173    174         for v in self:   175    176             # Return True if the current value is equal to the specified one.   177             # Note that this is not an identity test, but an equality test.   178    179             if v == value:   180                 return True   181    182         return False   183    184     def index(self, value):   185    186         "Return the index of 'value' or raise ValueError."   187    188         i = 0   189         l = len(self)   190         while i < l:   191             if self[i] == value:   192                 return i   193             i += 1   194    195         raise ValueError(value)   196    197     def __eq__(self, other):   198    199         "Return whether this sequence is equal to 'other'."   200    201         # Sequences must have equal lengths to be equal.   202    203         n = self.__len__()   204         if len(other) != n:   205             return False   206    207         i = 0   208         while i < n:   209             if self.__getitem__(i) != other.__getitem__(i):   210                 return False   211             i += 1   212    213         return True   214    215     def __ne__(self, other):   216    217         "Return whether this sequence is not equal to 'other'."   218    219         return not self.__eq__(other)   220    221     def __iter__(self):   222    223         "Method to be overridden by subclasses."   224    225         raise StopIteration()   226    227 def _get_absolute_index(index, length):   228    229     """   230     Return the absolute index for 'index' given a collection having the   231     specified 'length'.   232     """   233    234     if index < 0:   235         return length + index   236     else:   237         return index   238    239 def _max(x, y):   240    241     "Return the maximum of 'x' and 'y'."   242    243     if x >= y:   244         return x   245     else:   246         return y   247    248 def _min(x, y):   249    250     "Return the minimum of 'x' and 'y'."   251    252     if x <= y:   253         return x   254     else:   255         return y   256    257 # vim: tabstop=4 expandtab shiftwidth=4