Repository: modin-project/modin

Issue:
`DataFrame.__getitem__` does not respect the `step` attribute of a slice argument
### System information
- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: Any
- **Modin version** (`modin.__version__`): 8d67cd3a467d1eb2262898f6f5e2cf159c27d7b0
- **Python version**: Any
- **Code we can use to reproduce**:
```python
import modin.pandas as pd

df = pd.DataFrame({"a": [1, 2, 3, 4], "b": [3, 4, 5, 6]})

print(f"Modin result:\n{df[::2]}\n")
print(f"Pandas result:\n{df._to_pandas()[::2]}\n")
```

Output:
```
Modin result:
   a  b
0  1  3
1  2  4
2  3  5
3  4  6

Pandas result:
   a  b
0  1  3
2  3  5
```

<!--
You can obtain the Modin version with

python -c "import modin; print(modin.__version__)"
-->

### Describe the problem
The problem is in this incorrect check for None-slice, it does not respect the `step` parameter:
https://github.com/modin-project/modin/blob/8d67cd3a467d1eb2262898f6f5e2cf159c27d7b0/modin/pandas/base.py#L2999-L3000


Discussion:

Resolving patch:
diff --git a/modin/core/dataframe/pandas/dataframe/dataframe.py b/modin/core/dataframe/pandas/dataframe/dataframe.py
--- a/modin/core/dataframe/pandas/dataframe/dataframe.py
+++ b/modin/core/dataframe/pandas/dataframe/dataframe.py
@@ -30,6 +30,7 @@
     find_common_type_cat as find_common_type,
 )
 from modin.pandas.indexing import is_range_like
+from modin.pandas.utils import is_full_grab_slice
 
 
 class PandasDataframe(object):
@@ -954,12 +955,7 @@ def _get_dict_of_block_index(self, axis, indices):
         if isinstance(indices, slice) or (is_range_like(indices) and indices.step == 1):
             # Converting range-like indexer to slice
             indices = slice(indices.start, indices.stop, indices.step)
-            # Detecting full-axis grab
-            if (
-                indices.start in (None, 0)
-                and indices.step in (None, 1)
-                and (indices.stop is None or indices.stop >= len(self.axes[axis]))
-            ):
+            if is_full_grab_slice(indices, sequence_len=len(self.axes[axis])):
                 return OrderedDict(
                     zip(
                         range(self._partitions.shape[axis]),
diff --git a/modin/pandas/base.py b/modin/pandas/base.py
--- a/modin/pandas/base.py
+++ b/modin/pandas/base.py
@@ -45,6 +45,7 @@
 import warnings
 import pickle as pkl
 
+from .utils import is_full_grab_slice
 from modin.utils import try_cast_to_pandas, _inherit_docstrings
 from modin.error_message import ErrorMessage
 from modin.pandas.utils import is_scalar
@@ -2996,7 +2997,11 @@ def _getitem_slice(self, key: slice):
         modin.pandas.BasePandasDataset
             Selected rows.
         """
-        if key.start is None and key.stop is None:
+        if is_full_grab_slice(
+            key,
+            # Avoid triggering shape computation for lazy executions
+            sequence_len=(None if self._query_compiler.lazy_execution else len(self)),
+        ):
             return self.copy()
         return self.iloc[key]
 
diff --git a/modin/pandas/utils.py b/modin/pandas/utils.py
--- a/modin/pandas/utils.py
+++ b/modin/pandas/utils.py
@@ -113,6 +113,35 @@ def is_scalar(obj):
     return not isinstance(obj, BasePandasDataset) and pandas_is_scalar(obj)
 
 
+def is_full_grab_slice(slc, sequence_len=None):
+    """
+    Check that the passed slice grabs the whole sequence.
+
+    Parameters
+    ----------
+    slc : slice
+        Slice object to check.
+    sequence_len : int, optional
+        Length of the sequence to index with the passed `slc`.
+        If not specified the function won't be able to check whether
+        ``slc.stop`` is equal or greater than the sequence length to
+        consider `slc` to be a full-grab, and so, only slices with
+        ``.stop is None`` are considered to be a full-grab.
+
+    Returns
+    -------
+    bool
+    """
+    assert isinstance(slc, slice), "slice object required"
+    return (
+        slc.start in (None, 0)
+        and slc.step in (None, 1)
+        and (
+            slc.stop is None or (sequence_len is not None and slc.stop >= sequence_len)
+        )
+    )
+
+
 def from_modin_frame_to_mi(df, sortorder=None, names=None):
     """
     Make a pandas.MultiIndex from a DataFrame.


Test patch:
diff --git a/modin/pandas/test/dataframe/test_indexing.py b/modin/pandas/test/dataframe/test_indexing.py
--- a/modin/pandas/test/dataframe/test_indexing.py
+++ b/modin/pandas/test/dataframe/test_indexing.py
@@ -1437,6 +1437,7 @@ def test___getitem__(data):
         (-3, -1),
         (1, -1, 2),
         (-1, 1, -1),
+        (None, None, 2),
     ]
 
     # slice test
diff --git a/modin/pandas/test/test_series.py b/modin/pandas/test/test_series.py
--- a/modin/pandas/test/test_series.py
+++ b/modin/pandas/test/test_series.py
@@ -352,6 +352,7 @@ def test___getitem__(data):
     pandas_series = pandas.Series(list(range(1000)))
     df_equals(modin_series[:30], pandas_series[:30])
     df_equals(modin_series[modin_series > 500], pandas_series[pandas_series > 500])
+    df_equals(modin_series[::2], pandas_series[::2])
 
     # Test empty series
     df_equals(pd.Series([])[:30], pandas.Series([])[:30])