'Returns a 2D array of cells, with optional row and column offsets.
:param range_string: cell range string or `named range` name
:type range_string: string
:param row: number of rows to offset
:type row: int
:param column: number of columns to offset
:type column: int
:rtype: tuples of tuples of :class:`openpyxl.cell.Cell`'

def range(self, range_string, row=0, column=0):

    if (':' in range_string):
        result = []
        (min_range, max_range) = range_string.split(':')
        (min_col, min_row) = coordinate_from_string(min_range)
        (max_col, max_row) = coordinate_from_string(max_range)
        if column:
            min_col = get_column_letter((column_index_from_string(min_col) + column))
            max_col = get_column_letter((column_index_from_string(max_col) + column))
        min_col = column_index_from_string(min_col)
        max_col = column_index_from_string(max_col)
        cache_cols = {}
        for col in xrange(min_col, (max_col + 1)):
            cache_cols[col] = get_column_letter(col)
        rows = xrange((min_row + row), ((max_row + row) + 1))
        cols = xrange(min_col, (max_col + 1))
        for row in rows:
            new_row = []
            for col in cols:
                new_row.append(self.cell(('%s%s' % (cache_cols[col], row))))
            result.append(tuple(new_row))
        return tuple(result)
    else:
        try:
            return self.cell(coordinate=range_string, row=row, column=column)
        except CellCoordinatesException:
            pass
        named_range = self._parent.get_named_range(range_string)
        if (named_range is None):
            msg = ('%s    is    not    a    valid    range    name' % range_string)
            raise NamedRangeException(msg)
        result = []
        for destination in named_range.destinations:
            (worksheet, cells_range) = destination
            if (worksheet is not self):
                msg = ('Range    %s    is    not    defined    on    worksheet    %s' % (cells_range, self.title))
                raise NamedRangeException(msg)
            content = self.range(cells_range)
            if isinstance(content, tuple):
                for cells in content:
                    result.extend(cells)
            else:
                result.append(content)
        if (len(result) == 1):
            return result[0]
        else:
            return tuple(result)
