# Buggy code
def fetch(self, session=None, **kw):
    batch_size = kw.pop("batch_size", 1000)
    if len(kw) > 0:  # pragma: no cover
        raise TypeError(
            f"'{next(iter(kw))}' is an invalid keyword argument for this function"
        )
    batches = list(self._iter(batch_size=batch_size, session=session))
    return pd.concat(batches) if len(batches) > 1 else batches[0]


# Traceback (ValueError)
In [20]: df = md.DataFrame(mt.random.rand(10, 3, chunk_size=3))

In [21]: df.execute()
Out[21]:
0         1         2
0  0.996454  0.199555  0.058095
1  0.856396  0.477889  0.869464
2  0.578357  0.449761  0.313753
3  0.907721  0.887646  0.171193
4  0.727089  0.617502  0.210623
5  0.209806  0.070762  0.183754
6  0.389748  0.779089  0.468244
7  0.506215  0.026623  0.473943
8  0.120368  0.201167  0.367040
9  0.717196  0.199664  0.741672

In [22]: df.iloc[:4].fetch(batch_size=3)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-22-3025f16f5892> in <module>
----> 1 df.iloc[:4].fetch(batch_size=3)

~/Workspace/mars/mars/dataframe/core.py in fetch(self, session, **kw)
449             raise TypeError(
450                 f"'{next(iter(kw))}' is an invalid keyword argument for this function")
--> 451         batches = list(self._iter(batch_size=batch_size, session=session))
452         return pd.concat(batches) if len(batches) > 1 else batches[0]
453

~/Workspace/mars/mars/dataframe/core.py in _iter(self, batch_size, session)
435             for i in range(n_batch):
436                 batch_data = iloc(self)[batch_size * i: batch_size * (i + 1)]
--> 437                 yield batch_data._fetch(session=session)
438         else:
439             yield self._fetch(session=session)

~/Workspace/mars/mars/core.py in _fetch(self, session, **kw)
406         session = self._get_session(session)
407         self._check_session(session, 'fetch')
--> 408         return session.fetch(self, **kw)
409
410     def fetch(self, session=None, **kw):

~/Workspace/mars/mars/session.py in fetch(self, *tileables, **kw)
532             ret_list = True
533
--> 534         result = self._sess.fetch(*tileables, **kw)
535
536         ret = []

~/Workspace/mars/mars/session.py in fetch(self, n_parallel, *tileables, **kw)
126         if n_parallel is None:
127             kw['n_parallel'] = cpu_count()
--> 128         return self._executor.fetch_tileables(tileables, **kw)
129
130     def fetch_log(self, tileables, offsets=None, sizes=None):  # pragma: no cover

~/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 fetch_tileables(self, tileables, **kw)
973                 # check if the tileable is executed before
974                 raise ValueError(
--> 975                     f'Tileable object {tileable.key} must be executed first before being fetched')
976
977         # if chunk executed, fetch chunk mechanism will be triggered in execute_tileables

ValueError: Tileable object 781bdacb33bac80f7e5b8f92ae3923ba must be executed first before being fetched

# Fixed code
def fetch(self, session=None, **kw):
    from .indexing.iloc import DataFrameIlocGetItem, SeriesIlocGetItem

    batch_size = kw.pop("batch_size", 1000)
    if len(kw) > 0:  # pragma: no cover
        raise TypeError(
            f"'{next(iter(kw))}' is an invalid keyword argument for this function"
        )

    if isinstance(self.op, (DataFrameIlocGetItem, SeriesIlocGetItem)):
        # see GH#1871
        # already iloc, do not trigger batch fetch
        return self._fetch(session=session, **kw)
    else:
        batches = list(self._iter(batch_size=batch_size, session=session))
        return pd.concat(batches) if len(batches) > 1 else batches[0]