[Tutor] is this use or abuse of __getitem__ ?

eryksun eryksun at gmail.com
Sat Sep 15 10:43:25 CEST 2012

On Fri, Sep 14, 2012 at 2:33 PM, Albert-Jan Roskam <fomcl at yahoo.com> wrote:
>> On 14/09/12 22:16, Albert-Jan Roskam wrote:
> Is it recommended to define the geitem() function inside the __getitem__() method?
> I was thinking I could also define a _getitem() private method.
>         def getitem(key):
>             retcode1 = self.iomodule.SeekNextCase(self.fh, ctypes.c_long(int(key)))
>             ....

I wouldn't do this since it incurs the cost of a repeated function
call. A slice could involve thousands of such calls. Maybe use a
boolean variable like "is_slice". Then use a for loop to build the
records list (maybe only 1 item). If is_slice, return records, else
return records[0].

>         if isinstance(key, slice):
>             records = [getitem(i) for i in range(*key.indices(self.nCases))]
>             return records
>         elif hasattr(key, "__int__"): # isinstance(key, (int, float)):
>             if abs(key) > (self.nCases - 1):
>                 raise IndexError
>             else:
>                 key = self.nCases + key if key < 0 else key
>                 record = getitem(key)
>                 return record
>         else:
>             raise TypeError

I agree with Steven's reasoning that it doesn't make sense to support
floating point indexes. Python 2.6+ has the __index__ special method.
int and long have this method. float, Decimal,and Fraction do not have
it. It lets you support any user-defined class that can be used as an
index. For example:

    >>> class MyInt(object):
    ...     def __index__(self):
    ...         return 5

    >>> slice(MyInt(), MyInt(), MyInt()).indices(10)
    (5, 5, 5)

operator.index() is the corresponding function. It raises TypeError if
__index__ isn't supported.

But watch out because you're using ctypes.c_long. It doesn't do any
range checking. It just silently wraps around modulo the size of a
long on your platform:

    >>> c_long(2**32-1), c_long(2**32), c_long(2**32+1)
    (c_long(-1), c_long(0), c_long(1))

Calling int(key) or index(key) is no help because it will silently
return a Python long (big int). You need to do range checking on the
upper bound and raise a ValueError.

For example:

    from operator import index  # calls obj.__index__()

    is_slice = isinstance(key, slice)

    if is_slice:
        start, stop, step = key.indices(self.nCases)  # may raise TypeError
        start = index(self.nCases + key if key < 0 else key)  # may
raise TypeError
        stop = start + 1
        step = 1

    if stop > 2 ** (ctypes.sizeof(ctypes.c_long) * 8 - 1):
        raise ValueError('useful message')

    records = []
    for i in range(start, stop, step):
        retcode1 = self.iomodule.SeekNextCase(self.fh, ctypes.c_long(i))
        self.caseBuffer, self.caseBufferPtr = self.getCaseBuffer()
        retcode2 = self.iomodule.WholeCaseIn(self.fh, self.caseBufferPtr)
        record = struct.unpack(self.structFmt, self.caseBuffer.raw)
        if any([retcode1, retcode2]):
            raise RuntimeError("Error retrieving record %d [%s, %s]" %
                (i, retcodes[retcode1], retcodes[retcode2]))

    if not is_slice:
        records = records[0]
    return records

More information about the Tutor mailing list