# Buggy code
def execute_map(cls, ctx, op):
    arg_axis = cls.get_arg_axis(op.axis, op.inputs[0].ndim)
    (in_chunk,), device_id, xp = as_same_device(
        [ctx[c.key] for c in op.inputs], device=op.device, ret_extra=True
    )

    func_name = getattr(cls, "_func_name")
    agg_func_name = getattr(cls, "_agg_func_name")
    arg_func = getattr(xp, func_name)
    agg_func_name = getattr(xp, agg_func_name)

    offset = op.offset
    chunk = op.outputs[0]
    with device(device_id):
        vals = agg_func_name(in_chunk, axis=arg_axis).reshape(chunk.shape)
        try:
            arg = arg_func(in_chunk, axis=arg_axis).reshape(chunk.shape)
        except ValueError:
            # handle all NaN
            arg = arg_func(
                xp.where(xp.isnan(in_chunk), np.inf, in_chunk), axis=arg_axis
            ).reshape(chunk.shape)

        if arg_axis is None:
            if xp == cp:
                # we need to copy to do cpu computation, then copy back to gpu
                # cuz unravel_index and ravel_multi_index are not implemented in cupy
                in_chunk = in_chunk.get()

            total_shape = op.total_shape
            ind = np.unravel_index(arg.ravel()[0], in_chunk.shape)
            total_ind = tuple(o + i for (o, i) in zip(offset, ind))
            res = np.ravel_multi_index(total_ind, total_shape)

            if xp == cp:
                # copy back
                with xp.cuda.Device(in_chunk.device.id):
                    arg[:] = xp.asarray(res)
            else:
                arg[:] = res
        else:
            arg += offset
        ctx[op.outputs[0].key] = (vals, arg)


# 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 execute_map(cls, ctx, op):
    arg_axis = cls.get_arg_axis(op.axis, op.inputs[0].ndim)
    (in_chunk,), device_id, xp = as_same_device(
        [ctx[c.key] for c in op.inputs], device=op.device, ret_extra=True
    )

    func_name = getattr(cls, "_func_name")
    agg_func_name = getattr(cls, "_agg_func_name")
    arg_func = getattr(xp, func_name)
    agg_func_name = getattr(xp, agg_func_name)

    offset = op.offset
    chunk = op.outputs[0]
    with device(device_id):
        vals = agg_func_name(in_chunk, axis=arg_axis)
        if hasattr(vals, "reshape"):
            vals = vals.reshape(chunk.shape)
        try:
            arg = arg_func(in_chunk, axis=arg_axis)
            if hasattr(arg, "reshape"):
                arg = arg.reshape(chunk.shape)
        except ValueError:
            # handle all NaN
            arg = arg_func(
                xp.where(xp.isnan(in_chunk), np.inf, in_chunk), axis=arg_axis
            ).reshape(chunk.shape)

        if arg_axis is None:
            if xp == cp:
                # we need to copy to do cpu computation, then copy back to gpu
                # cuz unravel_index and ravel_multi_index are not implemented in cupy
                in_chunk = in_chunk.get()

            total_shape = op.total_shape
            ind = np.unravel_index(arg.ravel()[0], in_chunk.shape)
            total_ind = tuple(o + i for (o, i) in zip(offset, ind))
            res = np.ravel_multi_index(total_ind, total_shape)

            if xp == cp:
                # copy back
                with xp.cuda.Device(in_chunk.device.id):
                    arg[:] = xp.asarray(res)
            else:
                arg[:] = res
        else:
            arg += offset
        ctx[op.outputs[0].key] = (vals, arg)