'Return portion of self defined by key. If the key involves a slice
then a list will be returned (if key is a single slice) or a matrix
(if key was a tuple involving a slice).
Examples
>>> from sympy import Matrix, I
>>> m = Matrix([
... [1, 2 + I],
... [3, 4    ]])
If the key is a tuple that doesn\'t involve a slice then that element
is returned:
>>> m[1, 0]
3
When a tuple key involves a slice, a matrix is returned. Here, the
first column is selected (all rows, column 0):
>>> m[:, 0]
Matrix([
[1],
[3]])
If the slice is not a tuple then it selects from the underlying
list of elements that are arranged in row order and a list is
returned if a slice is involved:
>>> m[0]
1
>>> m[::2]
[1, 3]'

def __getitem__(self, key):

    if isinstance(key, tuple):
        (i, j) = key
        try:
            (i, j) = self.key2ij(key)
            return self._mat[((i * self.cols) + j)]
        except (TypeError, IndexError):
            if ((isinstance(i, Expr) and (not i.is_number)) or (isinstance(j, Expr) and (not j.is_number))):
                if (((j < 0) is True) or ((j >= self.shape[1]) is True) or ((i < 0) is True) or ((i >= self.shape[0]) is True)):
                    raise ValueError('index    out    of    boundary')
                from sympy.matrices.expressions.matexpr import MatrixElement
                return MatrixElement(self, i, j)
            if isinstance(i, slice):
                i = list(range(self.rows))[i]
            elif is_sequence(i):
                pass
            else:
                i = [i]
            if isinstance(j, slice):
                j = list(range(self.cols))[j]
            elif is_sequence(j):
                pass
            else:
                j = [j]
            return self.extract(i, j)
    else:
        if isinstance(key, slice):
            return self._mat[key]
        return self._mat[a2idx(key)]
