# Buggy code
def sum(a, axis=None, dtype=None, out=None, keepdims=None, combine_size=None):
    """
    Sum of tensor elements over a given axis.

    Parameters
    ----------
    a : array_like
        Elements to sum.
    axis : None or int or tuple of ints, optional
        Axis or axes along which a sum is performed.  The default,
        axis=None, will sum all of the elements of the input tensor.  If
        axis is negative it counts from the last to the first axis.

        If axis is a tuple of ints, a sum is performed on all of the axes
        specified in the tuple instead of a single axis or all the axes as
        before.
    dtype : dtype, optional
        The type of the returned tensor and of the accumulator in which the
        elements are summed.  The dtype of `a` is used by default unless `a`
        has an integer dtype of less precision than the default platform
        integer.  In that case, if `a` is signed then the platform integer
        is used while if `a` is unsigned then an unsigned integer of the
        same precision as the platform integer is used.
    out : Tensor, optional
        Alternative output tensor in which to place the result. It must have
        the same shape as the expected output, but the type of the output
        values will be cast if necessary.
    keepdims : bool, optional
        If this is set to True, the axes which are reduced are left
        in the result as dimensions with size one. With this option,
        the result will broadcast correctly against the input tensor.

        If the default value is passed, then `keepdims` will not be
        passed through to the `sum` method of sub-classes of
        `Tensor`, however any non-default value will be.  If the
        sub-classes `sum` method does not implement `keepdims` any
        exceptions will be raised.
    combine_size: int, optional
        The number of chunks to combine.

    Returns
    -------
    sum_along_axis : Tensor
        An array with the same shape as `a`, with the specified
        axis removed.   If `a` is a 0-d tensor, or if `axis` is None, a scalar
        is returned.  If an output array is specified, a reference to
        `out` is returned.

    See Also
    --------
    Tensor.sum : Equivalent method.

    cumsum : Cumulative sum of tensor elements.

    trapz : Integration of tensor values using the composite trapezoidal rule.

    mean, average

    Notes
    -----
    Arithmetic is modular when using integer types, and no error is
    raised on overflow.

    The sum of an empty array is the neutral element 0:

    >>> import mars.tensor as mt

    >>> mt.sum([]).execute()
    0.0

    Examples
    --------
    >>> mt.sum([0.5, 1.5]).execute()
    2.0
    >>> mt.sum([0.5, 0.7, 0.2, 1.5], dtype=mt.int32).execute()
    1
    >>> mt.sum([[0, 1], [0, 5]]).execute()
    6
    >>> mt.sum([[0, 1], [0, 5]], axis=0).execute()
    array([0, 6])
    >>> mt.sum([[0, 1], [0, 5]], axis=1).execute()
    array([1, 5])

    If the accumulator is too small, overflow occurs:

    >>> mt.ones(128, dtype=mt.int8).sum(dtype=mt.int8).execute()
    -128

    """
    a = astensor(a)
    if dtype is None:
        dtype = np.empty((1,), dtype=a.dtype).sum().dtype
    else:
        dtype = np.dtype(dtype)
    op = TensorSum(axis=axis, dtype=dtype, keepdims=keepdims, combine_size=combine_size)
    return op(a, out=out)


# 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 sum(a, axis=None, dtype=None, out=None, keepdims=None, combine_size=None):
    """
    Sum of tensor elements over a given axis.

    Parameters
    ----------
    a : array_like
        Elements to sum.
    axis : None or int or tuple of ints, optional
        Axis or axes along which a sum is performed.  The default,
        axis=None, will sum all of the elements of the input tensor.  If
        axis is negative it counts from the last to the first axis.

        If axis is a tuple of ints, a sum is performed on all of the axes
        specified in the tuple instead of a single axis or all the axes as
        before.
    dtype : dtype, optional
        The type of the returned tensor and of the accumulator in which the
        elements are summed.  The dtype of `a` is used by default unless `a`
        has an integer dtype of less precision than the default platform
        integer.  In that case, if `a` is signed then the platform integer
        is used while if `a` is unsigned then an unsigned integer of the
        same precision as the platform integer is used.
    out : Tensor, optional
        Alternative output tensor in which to place the result. It must have
        the same shape as the expected output, but the type of the output
        values will be cast if necessary.
    keepdims : bool, optional
        If this is set to True, the axes which are reduced are left
        in the result as dimensions with size one. With this option,
        the result will broadcast correctly against the input tensor.

        If the default value is passed, then `keepdims` will not be
        passed through to the `sum` method of sub-classes of
        `Tensor`, however any non-default value will be.  If the
        sub-classes `sum` method does not implement `keepdims` any
        exceptions will be raised.
    combine_size: int, optional
        The number of chunks to combine.

    Returns
    -------
    sum_along_axis : Tensor
        An array with the same shape as `a`, with the specified
        axis removed.   If `a` is a 0-d tensor, or if `axis` is None, a scalar
        is returned.  If an output array is specified, a reference to
        `out` is returned.

    See Also
    --------
    Tensor.sum : Equivalent method.

    cumsum : Cumulative sum of tensor elements.

    trapz : Integration of tensor values using the composite trapezoidal rule.

    mean, average

    Notes
    -----
    Arithmetic is modular when using integer types, and no error is
    raised on overflow.

    The sum of an empty array is the neutral element 0:

    >>> import mars.tensor as mt

    >>> mt.sum([]).execute()
    0.0

    Examples
    --------
    >>> mt.sum([0.5, 1.5]).execute()
    2.0
    >>> mt.sum([0.5, 0.7, 0.2, 1.5], dtype=mt.int32).execute()
    1
    >>> mt.sum([[0, 1], [0, 5]]).execute()
    6
    >>> mt.sum([[0, 1], [0, 5]], axis=0).execute()
    array([0, 6])
    >>> mt.sum([[0, 1], [0, 5]], axis=1).execute()
    array([1, 5])

    If the accumulator is too small, overflow occurs:

    >>> mt.ones(128, dtype=mt.int8).sum(dtype=mt.int8).execute()
    -128

    """
    a = astensor(a)
    if dtype is None:
        if a.dtype == np.object_:
            dtype = a.dtype
        else:
            dtype = np.empty((1,), dtype=a.dtype).sum().dtype
    else:
        dtype = np.dtype(dtype)
    op = TensorSum(axis=axis, dtype=dtype, keepdims=keepdims, combine_size=combine_size)
    return op(a, out=out)