2

I have a class which keeps track of errors encountered during a search operation

class SearchError(object): 
    def __init__(self,severity=0,message=''): 
        self.severity = severity
        self.message = message

My idea is to make the instance variables indexable.

So if I have

a=SearchError(1,"Fatal Error")

I get

>>> a[0]
1
>>> a[1]
'Fatal Error'
>>> a.severity
1
>>> a.message
'Fatal Error'

To do this I add a __getitem__ method to the class. The class now becomes

class SearchError(object): 
    def __init__(self,severity=0,message=''): 
        self.severity = severity
        self.message = message

    def __getitem__(self,val): 
        if isinstance(val,slice): 
            return [self.__getitem__(i) for i in xrange(val.start,val.stop,val.step)]
        elif val==0: 
            return self.severity
        elif val==1: 
            return self.message
        else: 
            raise IndexError

This does what I want but fails in cases such as

>>> a[:2]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 23, in __getitem__
TypeError: an integer is required

Or even

>>> a[-1]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 29, in __getitem__
IndexError

I understand my implementation of __getitem__ is limited. What I need to know is -

  1. Is this the way to make instance variables indexable (Without using a list as variable container)?
  2. How do I make the object behave 'sanely' as far as indexing goes?

4 Answers 4

1

This does everything:

>>> from collections import namedtuple
>>> _SearchError = namedtuple("SearchError", "severity message")
>>> def SearchError(severity=0, message=''):
        return _SearchError(severity, message)
Sign up to request clarification or add additional context in comments.

2 Comments

Note that here your objects are immutable as a consequence of them being a tuple. That may be fine for OP's application, but it might not so I think it is worth mentioning.
What if the class SearchError also has some methods?
1

xrange requires all its arguments to be integers, but slice objects have None for unspecified attributes.

The best way to implement what you're after is to use namedtuple:

from collections import namedtuple
class SearchError(namedtuple('SearchError', 'severity message')):
    def __new__(cls, severity=0, message=''):
        return super(SearchError, cls).__new__(cls, severity, message)

Comments

1

The problem here is that slice objects default to having None values as attributes. So, a[:2] passes in slice(None,2,None). When you break this apart and try to pass it to xrange, you'll get a TypeError:

>>> xrange(None,2,None)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: an integer is required

Try a[0:2:1] and your code will work. Ultimately, you could do something like:

val.start = 0 if val.start is None else val.start
val.stop = 2 if val.stop is None else val.stop
val.stop = 2-val.stop if val.stop < 0 else val.stop
val.step = 1 if val.step is None else val.step

to unravel your slices into useable indices (In the general case, it'd be better to use len(self) instead of 2, but I don't know if your object has defined __len__.

Or, even better:

start,stop,step = val.indices(len(self))

Similarly, in the case where you do a[-1], you're not passing in a slice, a 0 or a 1, so you hit the else clause where you to raise an IndexError.

2 Comments

Thanks for clearing the slice part. I got why a[-1] raises an indexerror but was just curious how can I make my objects behave like other indexable objects (lists,tuples). I also read somewhere that slice has a method (probably indices() or something) which chops off non-relevant indices and produces sane defaults. But then I would need to define the number of variables (length of the indexable)
@RedBaron -- Thanks for the .indices method. I hadn't read about that one (although I was always certain that something like that must exist). I'm glad to know what it is now -- That's a gem.
0

I mucked around the code and found the following solution.

It uses lists but to only store the names of the variables - Not the actual values. Additionally it also provides the method add to add a new variable with a given name and value. The new variable will also be indexable. (The add function is not needed by my class, but is nice to have around)

Thanks to @mgilson for nudging me in this direction

class SearchError(object): 
    def __init__(self,severity=0,message=''): 
        self.severity = severity
        self.message = message
        self._length = 2
        self._vars_list = ['severity','message',]

    def __getitem__(self,val): 
        if isinstance(val,slice): 
            steps = val.indices(self._length)
            return [self.__getitem__(i) for i in xrange(*steps)]
        elif val < 0: 
            i = self._length + val
            if i < 0: 
                raise IndexError,"Index Out of range for SearchError object"
            else: 
                return self.__getitem__(i)    
        else: 
            try: 
                return getattr(self,self._vars_list[val])
            except IndexError: 
                raise IndexError,"Index Out of range for SearchError object"

    def add(self,var_name,value): 
        self._vars_list.append(var_name)
        self._length += 1
        setattr(self,var_name,value)

The results

>>> a=SearchError(1,"Fatal Error")
>>> a[-1]
'Fatal Error'
>>> a[-2]
1
>>> a[-3]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 14, in __getitem__
IndexError: Index Out of range for SearchError object
>>> a[2]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 21, in __getitem__
IndexError: Index Out of range for SearchError object
>>> a[1]
'Fatal Error'
>>> a[0]
1
>>> a[:]
[1, 'Fatal Error']
>>> a.add('new_severity',8)
>>> a[:]
[1, 'Fatal Error', 8]
>>> a[3]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 21, in __getitem__
IndexError: Index Out of range for SearchError object
>>> a[2]
8
>>> a.new_severity
8
>>> a[:3]
[1, 'Fatal Error', 8]
>>> a[:4]
[1, 'Fatal Error', 8]
>>> a[:2]
[1, 'Fatal Error']

As far as I can see, you need lists (to either store the actual variables or their names). If someone has a better alternative please do post

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.