6
\$\begingroup\$

Note that this code is not going to be used for anything, it's more of a thought experiment. I've been seeing a lot of NumPy code lately and noticed that they use a custom indexing process, presumably by defining an object's __getitem__ method. I wrote the following class that allows for a multi-dimensional array to be indexed as ls[1,2] instead of ls[1][2].

Is there a better way to write this method? It seems unnecessary to be keeping track of current as the item look-up is descended, but I can't think of a way to avoid it.

class Array(list):
    def __getitem__(self, indices):
        current = super(self.__class__, self)
        for index in indices:
            current = current.__getitem__(index)
        return current

a = Array([[[1,2,3],[4,5,6]],
           [[0,1,2],[9,8,7]]])
print a[1,0,2] # 2
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Actually, I find your implementation very wise and uncomplicated. It maybe just misses some comments to make it obvious. \$\endgroup\$
    – Cilyan
    Feb 9, 2016 at 22:14

2 Answers 2

4
\$\begingroup\$

Let me start by saying that there's nothing wrong with your approach, and almost nothing wrong with your implementation. Just two things I would like to remark.

Use of super.

Because of your use of super, you actively prevent subclassing. In Python 2.7, you're supposed to spell the name out long:

current = super(Array, self)

Consider this:

class Brray(Array): pass
b = Brray([[[1,2,3],[4,5,6]],
           [[0,1,2],[9,8,7]]])
print b[1, 0, 2]

This gives us

TypeError: 'int' object is not iterable

By using super(Array, self) instead, everything is fine again.

Subclassing list

You're subclassing list, which causes a = Array(...) to make a shallow copy of the supplied iterable. I myself would prefer composition:

class Array(object):
    def __init__(self, wrapped):
        self._wrapped = wrapped
    def __getitem__(self, indices):
        current = self._wrapped
        for index in indices:
            current = current[index]
        return current

(This is also aided by the fact that I don't like calling 'dunder' (double under) methods directly.

\$\endgroup\$
2
\$\begingroup\$

While your code is really great and i made an alternative using python mapping. The reason is that map() is like "for" in C code as explained in the article below.

https://wiki.python.org/moin/PythonSpeed/PerformanceTips#Loops

class Array(list):
    def __mapItem(self, index):
        # we are creating a function in order to use map()
        # which does exactly the same thing as yours
        self.current = self.current.__getitem__(index)

    def __getitem__(self, indices):
        self.current = super(self.__class__, self)
        # map every index to the target item using the __mapItem function
        map(self.__mapItem, indices)
        return self.current

a = Array([[[1,2,3],[4,5,6]],
           [[0,1,2],[9,8,7]]])
print a[1,0,2] # 2
\$\endgroup\$
5
  • 1
    \$\begingroup\$ That's interesting, I'm going to try timing them both and see what happens. \$\endgroup\$ Feb 9, 2016 at 22:34
  • 1
    \$\begingroup\$ It seems like the original class performs look-ups twice as quickly. I'd assume that this is because of the instance variable used in your solution. The dis code isn't terribly enlightening because I'm not terribly familiar with it, but it seems like a major difference is the use of LOAD_FAST and STORE_FAST versus LOAD_ATTR and STORE_ATTR. \$\endgroup\$ Feb 9, 2016 at 22:46
  • 1
    \$\begingroup\$ I just timed both in Python 3.4, and your class is quicker by about 10% there, so it seems like the changes to map between versions might be the big difference. \$\endgroup\$ Feb 9, 2016 at 22:56
  • \$\begingroup\$ I see , thanks for taking the time to check this out , i don't actually use python 3 and i can't really explain why it is slower in python 2 although it seems like it performs less steps based on dis ( 4 ). \$\endgroup\$ Feb 9, 2016 at 23:07
  • 1
    \$\begingroup\$ It's an interesting thought experiment, but I'd really like to say 'No!!!!!' to this approach, mainly based on the fact that __getitem__ influences class state, and the fact that map is used with a method that has side-effects (changing current). I greatly prefer the original approach over this. \$\endgroup\$ Feb 9, 2016 at 23:37

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.