# Buggy code
def tile(cls, op):
    from ..indexing.slice import TensorSlice

    in_tensor = op.inputs[0]
    out_tensor = op.outputs[0]
    axis = op.axis
    if not isinstance(axis, int):
        raise ValueError("axis must be a integer")
    axis = validate_axis(in_tensor.ndim, axis)
    if axis is None:
        raise NotImplementedError

    op_type, bin_op_type = getattr(op, "_get_op_types")()

    chunks = []
    for c in in_tensor.chunks:
        chunk_op = op_type(axis=op.axis, dtype=op.dtype)
        chunks.append(
            chunk_op.new_chunk(
                [c], shape=c.shape, index=c.index, order=out_tensor.order
            )
        )
    inter_tensor = copy.copy(in_tensor)
    inter_tensor._chunks = chunks

    slc = tuple(
        slice(None) if i != axis else slice(-1, None) for i in range(in_tensor.ndim)
    )

    output_chunks = []
    for chunk in chunks:
        if chunk.index[axis] == 0:
            output_chunks.append(chunk)
            continue

        to_cum_chunks = [chunk]
        for i in range(chunk.index[axis]):
            to_cum_index = chunk.index[:axis] + (i,) + chunk.index[axis + 1 :]
            shape = chunk.shape[:axis] + (1,) + chunk.shape[axis + 1 :]
            to_cum_chunk = inter_tensor.cix[to_cum_index]
            slice_op = TensorSlice(slices=slc, dtype=chunk.dtype)
            sliced_chunk = slice_op.new_chunk(
                [to_cum_chunk], shape=shape, index=to_cum_index, order=out_tensor.order
            )
            to_cum_chunks.append(sliced_chunk)

        bin_op = bin_op_type(dtype=chunk.dtype)
        output_chunk = bin_op.new_chunk(
            to_cum_chunks, shape=chunk.shape, index=chunk.index, order=out_tensor.order
        )
        output_chunks.append(output_chunk)

    new_op = op.copy()
    return new_op.new_tensors(
        op.inputs,
        in_tensor.shape,
        order=out_tensor.order,
        chunks=output_chunks,
        nsplits=in_tensor.nsplits,
    )


# Traceback (AttributeError)
In [5]: a = mt.tensor(['a', 'b', 'c'], dtype=object)

In [6]: a.max().execute()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-6-d9ebfaf2dc7b> in <module>
----> 1 a.max().execute()

~/Workspace/mars/mars/core.py in execute(self, session, **kw)
641
642         if wait:
--> 643             return run()
644         else:
645             thread_executor = ThreadPoolExecutor(1)

~/Workspace/mars/mars/core.py in run()
637
638         def run():
--> 639             self.data.execute(session, **kw)
640             return self
641

~/Workspace/mars/mars/core.py in execute(self, session, **kw)
377
378         if wait:
--> 379             return run()
380         else:
381             # leverage ThreadPoolExecutor to submit task,

~/Workspace/mars/mars/core.py in run()
372         def run():
373             # no more fetch, thus just fire run
--> 374             session.run(self, **kw)
375             # return Tileable or ExecutableTuple itself
376             return self

~/Workspace/mars/mars/session.py in run(self, *tileables, **kw)
497         tileables = tuple(mt.tensor(t) if not isinstance(t, (Entity, Base)) else t
498                           for t in tileables)
--> 499         result = self._sess.run(*tileables, **kw)
500
501         for t in tileables:

~/Workspace/mars/mars/session.py in run(self, *tileables, **kw)
106             # set number of running cores
107             self.context.set_ncores(kw['n_parallel'])
--> 108             res = self._executor.execute_tileables(tileables, **kw)
109             return res
110

~/Workspace/mars/mars/utils.py in _inner(*args, **kwargs)
449         def _inner(*args, **kwargs):
450             with self:
--> 451                 return func(*args, **kwargs)
452
453         return _inner

~/Workspace/mars/mars/executor.py in execute_tileables(self, tileables, fetch, n_parallel, n_thread, print_progress, mock, compose, name)
879                                    n_parallel=n_parallel or n_thread,
880                                    print_progress=print_progress, mock=mock,
--> 881                                    chunk_result=chunk_result)
882
883                 # update shape of tileable and its chunks whatever it's successful or not

~/Workspace/mars/mars/executor.py in execute_graph(self, graph, keys, n_parallel, print_progress, mock, no_intermediate, compose, retval, chunk_result)
691             print_progress=print_progress, mock=mock, mock_max_memory=self._mock_max_memory,
692             fetch_keys=fetch_keys, no_intermediate=no_intermediate)
--> 693         res = graph_execution.execute(retval)
694         self._mock_max_memory = max(self._mock_max_memory, graph_execution._mock_max_memory)
695         if mock:

~/Workspace/mars/mars/executor.py in execute(self, retval)
572         # wait until all the futures completed
573         for future in executed_futures:
--> 574             future.result()
575
576         if retval:

~/miniconda3/lib/python3.7/concurrent/futures/_base.py in result(self, timeout)
426                 raise CancelledError()
427             elif self._state == FINISHED:
--> 428                 return self.__get_result()
429
430             self._condition.wait(timeout)

~/miniconda3/lib/python3.7/concurrent/futures/_base.py in __get_result(self)
382     def __get_result(self):
383         if self._exception:
--> 384             raise self._exception
385         else:
386             return self._result

~/miniconda3/lib/python3.7/concurrent/futures/thread.py in run(self)
55
56         try:
---> 57             result = self.fn(*self.args, **self.kwargs)
58         except BaseException as exc:
59             self.future.set_exception(exc)

~/Workspace/mars/mars/utils.py in _inner(*args, **kwargs)
449         def _inner(*args, **kwargs):
450             with self:
--> 451                 return func(*args, **kwargs)
452
453         return _inner

~/Workspace/mars/mars/executor.py in _execute_operand(self, op)
444             # so we pass the first operand's first output to Executor.handle
445             first_op = ops[0]
--> 446             self.handle_op(first_op, results, self._mock)
447
448             # update maximal memory usage during execution

~/Workspace/mars/mars/executor.py in handle_op(self, *args, **kw)
376
377     def handle_op(self, *args, **kw):
--> 378         return Executor.handle(*args, **kw)
379
380     def _order_starts(self):

~/Workspace/mars/mars/executor.py in handle(cls, op, results, mock)
642                 # The `UFuncTypeError` was introduced by numpy#12593 since v1.17.0.
643                 try:
--> 644                     return runner(results, op)
645                 except UFuncTypeError as e:
646                     raise TypeError(str(e)).with_traceback(sys.exc_info()[2]) from None

~/Workspace/mars/mars/tensor/reduction/core.py in execute(cls, ctx, op)
288             return cls.execute_agg(ctx, op)
289         else:
--> 290             return cls.execute_one_chunk(ctx, op)
291
292

~/Workspace/mars/mars/tensor/reduction/core.py in execute_one_chunk(cls, ctx, op)
277     @classmethod
278     def execute_one_chunk(cls, ctx, op):
--> 279         cls.execute_agg(ctx, op)
280
281     @classmethod

~/Workspace/mars/mars/tensor/reduction/core.py in execute_agg(cls, ctx, op)
273                                   keepdims=bool(op.keepdims))
274
--> 275             ctx[out.key] = ret.astype(op.dtype, order=out.order.value, copy=False)
276
277     @classmethod

AttributeError: 'str' object has no attribute 'astype'

# Fixed code
def tile(cls, op):
    from ..indexing.slice import TensorSlice

    in_tensor = op.inputs[0]
    out_tensor = op.outputs[0]
    axis = op.axis
    if not isinstance(axis, int):
        raise ValueError("axis must be a integer")
    axis = validate_axis(in_tensor.ndim, axis)
    if axis is None:
        raise NotImplementedError

    op_type, bin_op_type = getattr(op, "_get_op_types")()

    chunks = []
    for c in in_tensor.chunks:
        chunk_op = op_type(axis=op.axis, dtype=op.dtype)
        chunks.append(
            chunk_op.new_chunk(
                [c], shape=c.shape, index=c.index, order=out_tensor.order
            )
        )
    inter_tensor = copy.copy(in_tensor)
    inter_tensor._chunks = chunks

    slc = tuple(
        slice(None) if i != axis else slice(-1, None) for i in range(in_tensor.ndim)
    )

    output_chunks = []
    for chunk in chunks:
        if chunk.index[axis] == 0:
            output_chunks.append(chunk)
            continue

        to_cum_chunks = []
        for i in range(chunk.index[axis]):
            to_cum_index = chunk.index[:axis] + (i,) + chunk.index[axis + 1 :]
            shape = chunk.shape[:axis] + (1,) + chunk.shape[axis + 1 :]
            to_cum_chunk = inter_tensor.cix[to_cum_index]
            slice_op = TensorSlice(slices=slc, dtype=chunk.dtype)
            sliced_chunk = slice_op.new_chunk(
                [to_cum_chunk], shape=shape, index=to_cum_index, order=out_tensor.order
            )
            to_cum_chunks.append(sliced_chunk)
        to_cum_chunks.append(chunk)

        bin_op = bin_op_type(dtype=chunk.dtype)
        output_chunk = bin_op.new_chunk(
            to_cum_chunks, shape=chunk.shape, index=chunk.index, order=out_tensor.order
        )
        output_chunks.append(output_chunk)

    new_op = op.copy()
    return new_op.new_tensors(
        op.inputs,
        in_tensor.shape,
        order=out_tensor.order,
        chunks=output_chunks,
        nsplits=in_tensor.nsplits,
    )