Repository: mars-project/mars

Issue:
[BUG] Serialize failed if FetchDataFrame's output_type is type of list
<!--
Thank you for your contribution!

Please review https://github.com/mars-project/mars/blob/master/CONTRIBUTING.rst before opening an issue.
-->

**Describe the bug**
Serialize failed when serialize fetch DataFrame.

**To Reproduce**
``` Python
raw = pd.DataFrame(np.random.rand(100, 3), columns=list('abc'))
df = md.DataFrame(raw)
bins = [0.006 * i for i in range(0, 101)]
cut = md.cut(df['a'], bins)
cut.execute()
# test fetch replacement
r = cut.iloc[:3].execute()
expected = pd.cut(raw['a'], bins).iloc[:3]
pd.testing.assert_series_equal(r.fetch(), expected)
```
run the above code in cluster mode will raise error:
```
Error
Traceback (most recent call last):
  File "mars/serialize/jsonserializer.pyx", line 588, in mars.serialize.jsonserializer.JsonSerializeProvider.serialize_field
  File "mars/serialize/jsonserializer.pyx", line 522, in mars.serialize.jsonserializer.JsonSerializeProvider._on_serial
AttributeError: 'list' object has no attribute 'value'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/case.py", line 59, in testPartExecutor
    yield
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/case.py", line 615, in run
    testMethod()
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/mock.py", line 1191, in patched
    return func(*args, **keywargs)
  File "/Users/hekaisheng/Documents/mars_dev/mars/mars/deploy/local/tests/test_cluster.py", line 553, in testFetchDataFrame
    r = cut.iloc[:3].execute()
  File "/Users/hekaisheng/Documents/mars_dev/mars/mars/core.py", line 561, in execute
    self._data.execute(session, **kw)
  File "/Users/hekaisheng/Documents/mars_dev/mars/mars/core.py", line 370, in execute
    session.run(self, **kw)
  File "/Users/hekaisheng/Documents/mars_dev/mars/mars/session.py", line 428, in run
    result = self._sess.run(*tileables, **kw)
  File "/Users/hekaisheng/Documents/mars_dev/mars/mars/session.py", line 287, in run
    self._api.submit_graph(self._session_id, json.dumps(graph.to_json(), separators=(',', ':')),
  File "mars/graph.pyx", line 452, in mars.graph.DirectedGraph.to_json
  File "mars/serialize/core.pyx", line 689, in mars.serialize.core.Serializable.to_json
  File "mars/serialize/core.pyx", line 660, in mars.serialize.core.Serializable.serialize
  File "mars/serialize/core.pyx", line 786, in mars.serialize.core.Provider.serialize_model
  File "mars/serialize/core.pyx", line 153, in mars.serialize.core.Field.serialize
  File "mars/serialize/core.pyx", line 154, in mars.serialize.core.Field.serialize
  File "mars/serialize/jsonserializer.pyx", line 584, in mars.serialize.jsonserializer.JsonSerializeProvider.serialize_field
  File "mars/serialize/jsonserializer.pyx", line 529, in mars.serialize.jsonserializer.JsonSerializeProvider._serialize_reference
  File "mars/serialize/core.pyx", line 724, in mars.serialize.core.AttributeAsDict.serialize
  File "mars/serialize/core.pyx", line 791, in mars.serialize.core.Provider.serialize_attribute_as_dict
  File "mars/serialize/core.pyx", line 786, in mars.serialize.core.Provider.serialize_model
  File "mars/serialize/core.pyx", line 153, in mars.serialize.core.Field.serialize
  File "mars/serialize/core.pyx", line 154, in mars.serialize.core.Field.serialize
  File "mars/serialize/jsonserializer.pyx", line 591, in mars.serialize.jsonserializer.JsonSerializeProvider.serialize_field
  File "mars/serialize/jsonserializer.pyx", line 588, in mars.serialize.jsonserializer.JsonSerializeProvider.serialize_field
  File "mars/serialize/jsonserializer.pyx", line 522, in mars.serialize.jsonserializer.JsonSerializeProvider._on_serial
AttributeError: Fail to serialize field `object_type` for DataFrameFetch <key=2670416e321242115cba4010ed862ce9>, reason: 'list' object has no attribute 'value'

```

**Expected behavior**
A clear and concise description of what you expected to happen.

**Additional context**
Add any other context about the problem here.



Discussion:

Resolving patch:
diff --git a/mars/dataframe/fetch/core.py b/mars/dataframe/fetch/core.py
--- a/mars/dataframe/fetch/core.py
+++ b/mars/dataframe/fetch/core.py
@@ -14,10 +14,10 @@
 
 import operator
 
-from ...serialize.core import TupleField, ValueType, Int8Field
+from ...serialize.core import TupleField, ValueType, Int8Field, AnyField
 from ...operands import Fetch, FetchShuffle, FetchMixin
 from ...utils import on_serialize_shape, on_deserialize_shape
-from ..operands import DataFrameOperandMixin, ObjectType
+from ..operands import DataFrameOperandMixin, ObjectType, on_deserialize_object_type, on_serialize_object_type
 
 
 class DataFrameFetchMixin(DataFrameOperandMixin, FetchMixin):
@@ -28,8 +28,8 @@ class DataFrameFetch(Fetch, DataFrameFetchMixin):
     # required fields
     _shape = TupleField('shape', ValueType.int64,
                         on_serialize=on_serialize_shape, on_deserialize=on_deserialize_shape)
-    _object_type = Int8Field('object_type', on_serialize=operator.attrgetter('value'),
-                             on_deserialize=ObjectType)
+    _object_type = AnyField('object_type', on_serialize=on_serialize_object_type,
+                            on_deserialize=on_deserialize_object_type)
 
     def __init__(self, to_fetch_key=None, sparse=False, object_type=None, **kw):
         super().__init__(


Test patch:
diff --git a/mars/deploy/local/tests/test_cluster.py b/mars/deploy/local/tests/test_cluster.py
--- a/mars/deploy/local/tests/test_cluster.py
+++ b/mars/deploy/local/tests/test_cluster.py
@@ -544,6 +544,16 @@ def testFetchDataFrame(self, *_):
             r2 = session.fetch(df6)
             pd.testing.assert_series_equal(r1, r2)
 
+            raw = pd.DataFrame(np.random.rand(100, 3), columns=list('abc'))
+            df = md.DataFrame(raw)
+            bins = [0.006 * i for i in range(0, 101)]
+            cut = md.cut(df['a'], bins)
+            cut.execute()
+            # test fetch replacement
+            r = cut.iloc[:3].execute()
+            expected = pd.cut(raw['a'], bins).iloc[:3]
+            pd.testing.assert_series_equal(r.fetch(), expected)
+
     def testMultiSessionDecref(self, *_):
         with new_cluster(scheduler_n_process=2, worker_n_process=2,
                          shared_memory='20M', web=True) as cluster: