Welcome, guest | Sign In | My Account | Store | Cart

A list that will only allow strings as members. Methods like count, __contains__ ( a in list ), remove, index etc are case insensitive. See also the caselessDict - http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/283455

Python, 218 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#  17-05-04
# v1.1.1
#

# caselessList
# A case insensitive list that only permits strings as keys.

# Implemented for ConfigObj
# Requires Python 2.2 or above

# Copyright Michael Foord
# Not for use in commercial projects without permission. (Although permission will probably be given).
# If you use in a non-commercial project then please credit me and include a link back.
# If you release the project non-commercially then let me know (and include this message with my code !)

# No warranty express or implied for the accuracy, fitness to purpose or otherwise for this code....
# Use at your own risk !!!

# E-mail fuzzyman AT atlantibots DOT org DOT uk (or michael AT foord DOT me DO

class caselessList(list):
    """A case insensitive lists that has some caseless methods. Only allows strings as list members.
    Most methods that would normally return a list, return a caselessList. (Except list() and lowercopy())
    Sequence Methods implemented are :
    __contains__, remove, count, index, append, extend, insert,
    __getitem__, __setitem__, __getslice__, __setslice__
    __add__, __radd__, __iadd__, __mul__, __rmul__
    Plus Extra methods:
    findentry, copy , lowercopy, list
    Inherited methods :
    __imul__, __len__, __iter__, pop, reverse, sort
    """
    def __init__(self, inlist=[]):
        list.__init__(self)
        for entry in inlist:
            if not isinstance(entry, str): raise TypeError('Members of this object must be strings. You supplied \"%s\" which is \"%s\"' % (entry, type(entry)))
            self.append(entry)

    def findentry(self, item):
        """A caseless way of checking if an item is in the list or not.
        It returns None or the entry."""
        if not isinstance(item, str): raise TypeError('Members of this object must be strings. You supplied \"%s\"' % type(item))
        for entry in self:
            if item.lower() == entry.lower(): return entry
        return None
    
    def __contains__(self, item):
        """A caseless way of checking if a list has a member in it or not."""
        for entry in self:
            if item.lower() == entry.lower(): return True
        return False
        
    def remove(self, item):
        """Remove the first occurence of an item, the caseless way."""
        for entry in self:
            if item.lower() == entry.lower():
                list.remove(self, entry)
                return
        raise ValueError(': list.remove(x): x not in list')
    
    def copy(self):
        """Return a caselessList copy of self."""
        return caselessList(self)

    def list(self):
        """Return a normal list version of self."""
        return list(self)
        
    def lowercopy(self):
        """Return a lowercase (list) copy of self."""
        return [entry.lower() for entry in self]
    
    def append(self, item):
        """Adds an item to the list and checks it's a string."""
        if not isinstance(item, str): raise TypeError('Members of this object must be strings. You supplied \"%s\"' % type(item))
        list.append(self, item)
        
    def extend(self, item):
        """Extend the list with another list. Each member of the list must be a string."""
        if not isinstance(item, list): raise TypeError('You can only extend lists with lists. You supplied \"%s\"' % type(item))
        for entry in item:
            if not isinstance(entry, str): raise TypeError('Members of this object must be strings. You supplied \"%s\"' % type(entry))
            list.append(self, entry)        

    def count(self, item):
        """Counts references to 'item' in a caseless manner.
        If item is not a string it will always return 0."""
        if not isinstance(item, str): return 0
        count = 0
        for entry in self:
            if item.lower() == entry.lower():
                count += 1
        return count    

    def index(self, item, minindex=0, maxindex=None):
        """Provide an index of first occurence of item in the list. (or raise a ValueError if item not present)
        If item is not a string, will raise a TypeError.
        minindex and maxindex are also optional arguments
        s.index(x[, i[, j]]) return smallest k such that s[k] == x and i <= k < j
        """
        if maxindex == None: maxindex = len(self)
        minindex = max(0, minindex)-1
        maxindex = min(len(self), maxindex)
        if not isinstance(item, str): raise TypeError('Members of this object must be strings. You supplied \"%s\"' % type(item))
        index = minindex
        while index < maxindex:
            index += 1
            if item.lower() == self[index].lower():
                return index
        raise ValueError(': list.index(x): x not in list')
    
    def insert(self, i, x):
        """s.insert(i, x) same as s[i:i] = [x]
        Raises TypeError if x isn't a string."""
        if not isinstance(x, str): raise TypeError('Members of this object must be strings. You supplied \"%s\"' % type(x))
        list.insert(self, i, x)

    def __setitem__(self, index, value):
        """For setting values in the list.
        index must be an integer or (extended) slice object. (__setslice__ used for simple slices)
        If index is an integer then value must be a string.
        If index is a slice object then value must be a list of strings - with the same length as the slice object requires.
        """
        if isinstance(index, int):
            if not isinstance(value, str): raise TypeError('Members of this object must be strings. You supplied \"%s\"' % type(value))
            list.__setitem__(self, index, value)
        elif isinstance(index, slice):
            if not hasattr(value, '__len__'): raise TypeError('Value given to set slice is not a sequence object.')
            for entry in value:
                if not isinstance(entry, str): raise TypeError('Members of this object must be strings. You supplied \"%s\"' % type(entry))
            list.__setitem__(self, index, value)
        else:
            raise TypeError('Indexes must be integers or slice objects.')

    def __setslice__(self, i, j, sequence):
        """Called to implement assignment to self[i:j]."""
        for entry in sequence:
            if not isinstance(entry, str): raise TypeError('Members of this object must be strings. You supplied \"%s\"' % type(entry))
        list.__setslice__(self, i, j, sequence)

    def __getslice__(self, i, j):
        """Called to implement evaluation of self[i:j].
        Although the manual says this method is deprecated - if I don't define it the list one is called.
        (Which returns a list - this returns a caselessList)"""
        return caselessList(list.__getslice__(self, i, j))

    def __getitem__(self, index):
        """For fetching indexes.
        If a slice is fetched then the list returned is a caselessList."""
        if not isinstance(index, slice):
            return list.__getitem__(self, index)
        else:
            return caselessList(list.__getitem__(self, index))
            
    def __add__(self, item):
        """To add a list, and return a caselessList.
        Every element of item must be a string."""
        return caselessList(list.__add__(self, item))

    def __radd__(self, item):
        """To add a list, and return a caselessList.
        Every element of item must be a string."""
        return caselessList(list.__add__(self, item))
    
    def __iadd__(self, item):
        """To add a list in place."""
        for entry in item: self.append(entry)        

    def __mul__(self, item):
        """To multiply itself, and return a caselessList.
        Every element of item must be a string."""
        return caselessList(list.__mul__(self, item))

    def __rmul__(self, item):
        """To multiply itself, and return a caselessList.
        Every element of item must be a string."""
        return caselessList(list.__rmul__(self, item))

####################################################################################

# brief test stuff
if __name__ == '__main__':
    print
    print 'caselessList Tests :'
    a = caselessList(['hello', 'HELLO', 'HellO'])
    print 'A caselessList : ', a
    print 'a.findentry(\'hELLO\') = ', a.findentry('hELLO')
    print '(prints the first entry that matches this)', '\n'
    print '\'HeLLo\' in a : ', 'HeLLo' in a, '\n'                   # tests __contains__
    a.remove('HeLlO')
    print 'a.remove(\'HeLlO\'), print a : ', a
    print 'type(a.copy()) : ', type(a.copy())
    print 'type(a.list()) : ', type(a.list())
    print 'a.lowercopy() : ', a.lowercopy()
    a.append('HeLlO')
    print 'a.append(\'HeLlO\'), print a : ', a
    a.extend([char for char in 'AaAaA'])
    print 'a.extend([char for char in \'AaAaA\']), print a, type(a) : '
    print a, ',', type(a)
    print 'a.count(\'A\') : ', a.count('A')
    print 'a.index(\'A\') : ', a.index('a')
    a.insert(1, 'WisH')
    print 'a.insert(1, \'WisH\') : ',a
    print
    print 'The __setitem__ method is only novel for extended slice operations.'
    a[0:10:3] = ['Fish', 'fIsh', 'fiSh']
    print "a[0:10:3] = ['Fish', 'fIsh', 'fiSh'] : ", a
    print
    print 'Most interesting thing about __getitem__ is that if you ask for a slice - it will be an instance of caselessList'
    print 'type(a[0:4:1]) : ', type(a[0:4:1])
    
        
"""
15-05-04       Version 1.1.0
Added caselessList a caseless List implementation.
Lot more work than dict actually - more methods to implement for a sequence object.
Changed module name from caselessDict to caseless.
"""

In parsing keywords from a config file that is created by a user I wanted to check if a keyword was already in a list.

In order to simplify things for the user I wanted to make the keywords case insensitive....... So I thought a caseless list would be a good companion to the caselessDict - http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/283455 It was actually harder to implement than caselessDict as sequences have more methods that might set values.

Example :

>>> a = caselessList(['fIsH', 'fish', 'FISH'])
>>> 'FiSh' in a
True
>>> a.count('FISh')
3

See ConfigObj at http://www.voidspace.org.uk/atlantibots/pythonutils.html

1 comment

Mark Mc Mahon 17 years, 6 months ago  # | flag

A more flexible way of doing this. Another way of achieving the same effect is to use a case insensitive string (much less code :-) and a normal list or dict. See recipe:

http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/194371
Created by Michael Foord on Tue, 18 May 2004 (PSF)
Python recipes (4591)
Michael Foord's recipes (20)

Required Modules

  • (none specified)

Other Information and Tasks