4

I'm trying to write a python (2.7) matrix module. (I know about numpy, this is just for fun.)

My Code:

from numbers import Number
import itertools

test2DMat = [[1,2,3],[4,5,6],[7,8,9]]
test3DMat = [[[1,2,3],[4,5,6],[7,8,9]],[[2,3,4],[5,6,7],[8,9,0]],[[9,8,7],[6,5,4],[3,2,1]]]

class Dim(list):
    def __new__(cls,inDim):
        # If every item in inDim is a number create a Vec
        if all(isinstance(item,Number) for item in inDim):
            #return Vec(inDim)
            return Vec.__new__(cls,inDim)

        # Otherwise create a Dim
        return list.__new__(cls,inDim)

    def __init__(self,inDim):
        # Make sure every item in inDim is iterable
        try:
            for item in inDim: iter(item)
        except TypeError:
            raise TypeError('All items in a Dim must be iterable')

        # Make sure every item in inDim has the same length
        # or that there are zero items in the list
        if len(set(len(item) for item in inDim)) > 1:
            raise ValueError('All lists in a Dim must be the same length')

        inDim = map(Dim,inDim)
        list.__init__(self,inDim)


class Vec(Dim):
    def __new__(cls,inDim):
        if cls.__name__ not in [Vec.__name__,Dim.__name__]:
            newMat = list.__new__(Vec,inDim)
            newMat.__init__(inDim)
            return newMat
        return list.__new__(Vec,inDim)

    def __init__(self,inDim):
        list.__init__(self,inDim)


class Matrix(Dim):
    def __new__(cls,inMat):
        return Dim.__new__(cls,inMat)

    def __init__(self,inMat):
        super(Matrix,self).__init__(inMat)

Current Functionality:

So far I have written a few classes, Matrix, Dim, and Vec. Matrix and Vec are both subclasses of Dim. When creating a matrix, one would first start out with a list of lists and they would create a matrix like:

>>> startingList = [[1,2,3],[4,5,6],[7,8,9]]
>>> matrix.Matrix(startingList)
[[1,2,3],[4,5,6],[7,8,9]]

This should create a Matrix. The created Matrix should contain multiple Dims all of the same length. Each of these Dims should contain multiple Dims all of the same length, etc. The last Dim, the one that contains numbers, should contain only numbers and should be a Vec instead of a Dim.

The Problem:

All of this works, for lists. If I were however, to use an iterator object instead (such as that returned by iter()) this does not function as I want it to.

For example:

>>> startingList = [[1,2,3],[4,5,6],[7,8,9]]
>>> matrix.Matrix(iter(startingList))    
[]

My Thoughts:

I'm fairly certain that this is happening because in Dim.__new__ I iterate over the input iterable which, when the same iterable is then passed to Matrix.__init__ it has already been iterated over and will therefore appear to be empty, resulting in the empty matrix that I get.

I have tried copying the iterator using itertools.tee(), but this also doesn't work because I don't actually call Matrix.__init__ it gets called implicitly when Matrix.__new__ returns and I therefore cannot call it with different parameters than those passed to Matrix.__init__. Everything I have thought of to do comes up against this same problem.

Is there any way for me to preserve the existing functionality and also allow matrix.Matrix() to be called with an iterator object?

Matt
  • 3,651
  • 3
  • 16
  • 35
  • Since your not showing the version that uses `tee`, have you read [its docs](http://docs.python.org/library/itertools.html#itertools.tee)? "Once `tee()` has made a split, the original iterable should not be used anywhere else; otherwise, the iterable could get advanced without the tee objects being informed. This itertool may require significant auxiliary storage (depending on how much temporary data needs to be stored). In general, if one iterator uses most or all of the data before another iterator starts, it is faster to use `list()` instead of `tee()`." – Fred Foo Oct 18 '12 at 15:40
  • You don't show us **which functionality** you're actually adding to your classes - you've just got constructors which validate data, otherwise your items are plain python lists! I'd just create factory functions (like `makeVector() makeDim() makeMatrix()`) which cast iterators to lists, validate data and return the properly formed list. – Alan Franzoni Oct 18 '12 at 15:53
  • @AlanFranzoni You are correct, for the code I posted. I have not implemented most of what I plan to, and I removed the rest as I didn't think it was pertinent to this question. Plain lists will not suffice. – Matt Oct 18 '12 at 16:00
  • 1
    @larsmans Yes, I have read that documentation. I didn't really expect it to work, but since I'm not exactly sure how `__new__` works (with the automatic calling of `__init__`) I thought I would give it a try. I also tried just making `inDim` into a list at the beginning of `Matrix.__new__` but that has the same problem (`__init__` gets called with the arguments passed to `__new__`) – Matt Oct 18 '12 at 16:02
  • @Matt you can take a similar approach even though you're adding functionality. Forget about `__new__()`, which adds (IMHO) an unnecessary layer of complexity, and use a factory function which casts the input to a list, performs a check on input and then returns the best possible type based on such input. – Alan Franzoni Oct 19 '12 at 08:02

2 Answers2

3

The key is that Vec.__init__ is getting called twice; once inside your __new__ method and once when you return it from the __new__ method. So if you mark it as already initialised and return early from Vec.__init__ if it is already initialised, then you can ignore the second call:

class A(object):
    def __new__(cls, param):
        return B.__new__(cls, param + 100)
class B(A):
    def __new__(cls, param):
        b = object.__new__(B)
        b.__init__(param)
        return b
    def __init__(self, param):
        if hasattr(self, 'param'):
            print "skipping __init__", self
            return
        self.param = param
print A(5).param
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • 1
    @Matt sorry, I wasn't clear - you *need* to call `__init__` inside your `__new__` method and suppress its later call. – ecatmur Oct 18 '12 at 16:20
0

What you would need to do is check if the variable that is passed in is a tuple or list. If it is then you can use it directly, otherwise you need to convert the iterator into a list/tuple.

if isinstance(inDim, collections.Sequence):
    pass
elif hastattr(inDim, '__iter__'): # this is better than using iter()
    inDim = tuple(inDim)
else:
    # item is not iterable

There is also a better way of checking that the length of all the lists are the same:

if len(inDim) > 0:
    len_iter = (len(item) for item in inDim)
    first_len = len_iter.next()
    for other_len in len_iter:
        if other_len != first_len:
            raise ValueError('All lists in a Dim must be the same length')
Nathan Villaescusa
  • 17,331
  • 4
  • 53
  • 56
  • 1
    `isinstance(inDim, collections.Sequence))` would be cleaner. – Fred Foo Oct 18 '12 at 15:41
  • http://stackoverflow.com/questions/1952464/in-python-how-do-i-determine-if-an-object-is-iterable It is possible for there to exist objects that are iterable that do not have an `__iter__` method. `__getitem__` is sufficient to be iterable. I'm not sure if I care about supporting such objects. (I could also probably just add another check for `__getitem__` to what you have) – Matt Oct 18 '12 at 15:53
  • Good point. I hadn't thought of using strings (which don't have `__iter__` but can be used with `iter()`) with your code. – Nathan Villaescusa Oct 18 '12 at 15:57