Repository: dgilland/pydash (language: python)

Issue:
Embrace the tuple
Would you be open to PR that switches `zip_`, `zip_with` and `to_pairs` to return list of tuples instead?

Using a list in python is very similar to using a tuple but in static typing having lists is a bit annoying.
For example:
```py
import pydash

d: dict[str, int] = {"key1": 1, "key2": 2}

pairs = pydash.to_pairs(d)  # this is list[list[str | int]]

first_key, first_value = pairs[0]  # first_key is `str | int` and first_value is `str | int`
```
This doesn't make much sense, we know `first_key` should be `str` and `first_value` should be `int`. If `to_pairs` returned list of tuples instead we could type it better and not have this problem.

Same with `zip_`:
```py
import pydash

zipped = pydash.zip_([1, 2, 3], ["hello", "hello", "hello"])
```
`zipped` is a list of lists of `str | int`, we lost the order on the type level, int and str are blend in the lists.

And I think the hardest to work with might be `zip_with`
```py
import pydash

def more_hello(s: str, time: int) -> str:
    return s.upper() * time

zipped = pydash.zip_with(
    ["hello", "hello", "hello"],
    [1, 2, 3],
    iteratee=more_hello  # type error here: cannot assign `str | int` to `str` and `str | int` to `int`
)
```
while this is valid at runtime, the type checker doesn't know that the first argument is `str` and second is `int` because it is all blend into the `list[str | int]`

This makes them really hard to use in a type checked context. Chaining functions with the result of these functions is very impractical. I think it would make a lot more sense for these functions to return tuples.


Discussion / hints:
Makes sense to me! I'm all in favor.

Are there any other places where a type change like this would also make sense/make things easier?

Fix patch:
diff --git a/src/pydash/arrays.py b/src/pydash/arrays.py
index d9540a5..03b1847 100644
--- a/src/pydash/arrays.py
+++ b/src/pydash/arrays.py
@@ -98,6 +98,9 @@
 
 T = t.TypeVar("T")
 T2 = t.TypeVar("T2")
+T3 = t.TypeVar("T3")
+T4 = t.TypeVar("T4")
+T5 = t.TypeVar("T5")
 SequenceT = t.TypeVar("SequenceT", bound=t.Sequence)
 MutableSequenceT = t.TypeVar("MutableSequenceT", bound=t.MutableSequence)
 
@@ -2387,9 +2390,34 @@ def unshift(array: t.List[T], *items: T2) -> t.List[t.Union[T, T2]]:
     return array  # type: ignore
 
 
-def unzip(array: t.Iterable[t.Iterable[T]]) -> t.List[t.List[T]]:
+@t.overload
+def unzip(array: t.Iterable[t.Tuple[T, T2]]) -> t.List[t.Tuple[T, T2]]:
+    ...
+
+
+@t.overload
+def unzip(array: t.Iterable[t.Tuple[T, T2, T3]]) -> t.List[t.Tuple[T, T2, T3]]:
+    ...
+
+
+@t.overload
+def unzip(array: t.Iterable[t.Tuple[T, T2, T3, T4]]) -> t.List[t.Tuple[T, T2, T3, T4]]:
+    ...
+
+
+@t.overload
+def unzip(array: t.Iterable[t.Tuple[T, T2, T3, T4, T5]]) -> t.List[t.Tuple[T, T2, T3, T4, T5]]:
+    ...
+
+
+@t.overload
+def unzip(array: t.Iterable[t.Iterable[t.Any]]) -> t.List[t.Tuple[t.Any, ...]]:
+    ...
+
+
+def unzip(array):
     """
-    The inverse of :func:`zip_`, this method splits groups of elements into lists composed of
+    The inverse of :func:`zip_`, this method splits groups of elements into tuples composed of
     elements from each group at their corresponding indexes.
 
     Args:
@@ -2400,24 +2428,38 @@ def unzip(array: t.Iterable[t.Iterable[T]]) -> t.List[t.List[T]]:
 
     Example:
 
-        >>> unzip([[1, 4, 7], [2, 5, 8], [3, 6, 9]])
-        [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
+        >>> unzip([(1, 4, 7), (2, 5, 8), (3, 6, 9)])
+        [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
 
     .. versionadded:: 1.0.0
+
+    .. versionchanged:: 8.0.0
+        Support list of tuples instead.
     """
     return zip_(*array)
 
 
 @t.overload
 def unzip_with(
-    array: t.Iterable[t.Iterable[T]],
+    array: t.Iterable[t.Tuple[T, T2]],
     iteratee: t.Union[
-        t.Callable[[T, T, int, t.List[T]], T2],
-        t.Callable[[T, T, int], T2],
-        t.Callable[[T, T], T2],
-        t.Callable[[T], T2],
+        t.Callable[[t.Union[T, T2, T3], t.Union[T, T2], int], T3],
+        t.Callable[[t.Union[T, T2, T3], t.Union[T, T2]], T3],
+        t.Callable[[t.Union[T, T2, T3]], T3],
     ],
-) -> t.List[T2]:
+) -> t.List[T3]:
+    ...
+
+
+@t.overload
+def unzip_with(
+    array: t.Iterable[t.Iterable[t.Any]],
+    iteratee: t.Union[
+        t.Callable[[t.Any, t.Any, int], T3],
+        t.Callable[[t.Any, t.Any], T3],
+        t.Callable[[t.Any], T3],
+    ],
+) -> t.List[T3]:
     ...
 
 
@@ -2425,15 +2467,15 @@ def unzip_with(
 def unzip_with(
     array: t.Iterable[t.Iterable[T]],
     iteratee: None = None,
-) -> t.List[t.List[T]]:
+) -> t.List[t.Tuple[T]]:
     ...
 
 
 def unzip_with(array, iteratee=None):
     """
     This method is like :func:`unzip` except that it accepts an iteratee to specify how regrouped
-    values should be combined. The iteratee is invoked with four arguments: ``(accumulator, value,
-    index, group)``.
+    values should be combined. The iteratee is invoked with three arguments: ``(accumulator, value,
+    index)``.
 
     Args:
         array: List to process.
@@ -2445,7 +2487,7 @@ def unzip_with(array, iteratee=None):
     Example:
 
         >>> from pydash import add
-        >>> unzip_with([[1, 10, 100], [2, 20, 200]], add)
+        >>> unzip_with([(1, 10, 100), (2, 20, 200)], add)
         [3, 30, 300]
 
     .. versionadded:: 3.3.0
@@ -2624,7 +2666,43 @@ def xor_with(array, *lists, **kwargs):
     )
 
 
-def zip_(*arrays: t.Iterable[T]) -> t.List[t.List[T]]:
+@t.overload
+def zip_(array1: t.Iterable[T], array2: t.Iterable[T2], /) -> t.List[t.Tuple[T, T2]]:
+    ...
+
+
+@t.overload
+def zip_(
+    array1: t.Iterable[T], array2: t.Iterable[T2], array3: t.Iterable[T3], /
+) -> t.List[t.Tuple[T, T2, T3]]:
+    ...
+
+
+@t.overload
+def zip_(
+    array1: t.Iterable[T], array2: t.Iterable[T2], array3: t.Iterable[T3], array4: t.Iterable[T4], /
+) -> t.List[t.Tuple[T, T2, T3, T4]]:
+    ...
+
+
+@t.overload
+def zip_(
+    array1: t.Iterable[T],
+    array2: t.Iterable[T2],
+    array3: t.Iterable[T3],
+    array4: t.Iterable[T4],
+    array5: t.Iterable[T5],
+    /,
+) -> t.List[t.Tuple[T, T2, T3, T4, T5]]:
+    ...
+
+
+@t.overload
+def zip_(*arrays: t.Iterable[t.Any]) -> t.List[t.Tuple[t.Any, ...]]:
+    ...
+
+
+def zip_(*arrays):
     """
     Groups the elements of each array at their corresponding indexes. Useful for separate data
     sources that are coordinated through matching array indexes.
@@ -2638,12 +2716,14 @@ def zip_(*arrays: t.Iterable[T]) -> t.List[t.List[T]]:
     Example:
 
         >>> zip_([1, 2, 3], [4, 5, 6], [7, 8, 9])
-        [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
+        [(1, 4, 7), (2, 5, 8), (3, 6, 9)]
 
     .. versionadded:: 1.0.0
+
+    .. versionchanged:: 8.0.0
+        Return list of tuples instead of list of lists.
     """
-    # zip returns as a list of tuples so convert to list of lists
-    return [list(item) for item in zip(*arrays)]
+    return list(zip(*arrays))
 
 
 @t.overload
@@ -2724,40 +2804,47 @@ def zip_object_deep(keys: t.Iterable[t.Any], values: t.Union[t.List[t.Any], None
 
 @t.overload
 def zip_with(
-    *arrays: t.Iterable[T],
+    array1: t.Iterable[T],
+    array2: t.Iterable[T2],
+    *,
     iteratee: t.Union[
-        t.Callable[[T, T, int, t.List[T]], T2],
-        t.Callable[[T, T, int], T2],
-        t.Callable[[T, T], T2],
-        t.Callable[[T], T2],
+        t.Callable[[T, T2, int], T3],
+        t.Callable[[T, T2], T3],
+        t.Callable[[T], T3],
     ],
-) -> t.List[T2]:
+) -> t.List[T3]:
     ...
 
 
 @t.overload
-def zip_with(*arrays: t.Iterable[T]) -> t.List[t.List[T]]:
+def zip_with(
+    *arrays: t.Iterable[t.Any],
+    iteratee: t.Union[
+        t.Callable[[t.Any, t.Any, int], T2],
+        t.Callable[[t.Any, t.Any], T2],
+        t.Callable[[t.Any], T2],
+    ],
+) -> t.List[T2]:
     ...
 
 
 @t.overload
 def zip_with(
     *arrays: t.Union[
-        t.Iterable[T],
-        t.Callable[[T, T, int, t.List[T]], T2],
-        t.Callable[[T, T, int], T2],
-        t.Callable[[T, T], T2],
-        t.Callable[[T], T2],
+        t.Iterable[t.Any],
+        t.Callable[[t.Any, t.Any, int], T2],
+        t.Callable[[t.Any, t.Any], T2],
+        t.Callable[[t.Any], T2],
     ],
-) -> t.List[t.Union[t.List[T], T2]]:
+) -> t.List[T2]:
     ...
 
 
 def zip_with(*arrays, **kwargs):
     """
     This method is like :func:`zip` except that it accepts an iteratee to specify how grouped values
-    should be combined. The iteratee is invoked with four arguments: ``(accumulator, value, index,
-    group)``.
+    should be combined. The iteratee is invoked with three arguments:
+    ``(accumulator, value, index)``.
 
     Args:
         *arrays: Lists to process.
diff --git a/src/pydash/chaining/all_funcs.pyi b/src/pydash/chaining/all_funcs.pyi
index c1e3145..3d7f90a 100644
--- a/src/pydash/chaining/all_funcs.pyi
+++ b/src/pydash/chaining/all_funcs.pyi
@@ -562,23 +562,49 @@ class AllFuncs:
     def unshift(self: "Chain[t.List[T]]", *items: T2) -> "Chain[t.List[t.Union[T, T2]]]":
         return self._wrap(pyd.unshift)(*items)
 
-    def unzip(self: "Chain[t.Iterable[t.Iterable[T]]]") -> "Chain[t.List[t.List[T]]]":
+    @t.overload
+    def unzip(self: "Chain[t.Iterable[t.Tuple[T, T2]]]") -> "Chain[t.List[t.Tuple[T, T2]]]": ...
+    @t.overload
+    def unzip(
+        self: "Chain[t.Iterable[t.Tuple[T, T2, T3]]]",
+    ) -> "Chain[t.List[t.Tuple[T, T2, T3]]]": ...
+    @t.overload
+    def unzip(
+        self: "Chain[t.Iterable[t.Tuple[T, T2, T3, T4]]]",
+    ) -> "Chain[t.List[t.Tuple[T, T2, T3, T4]]]": ...
+    @t.overload
+    def unzip(
+        self: "Chain[t.Iterable[t.Tuple[T, T2, T3, T4, T5]]]",
+    ) -> "Chain[t.List[t.Tuple[T, T2, T3, T4, T5]]]": ...
+    @t.overload
+    def unzip(
+        self: "Chain[t.Iterable[t.Iterable[t.Any]]]",
+    ) -> "Chain[t.List[t.Tuple[t.Any, ...]]]": ...
+    def unzip(self):
         return self._wrap(pyd.unzip)()
 
     @t.overload
     def unzip_with(
-        self: "Chain[t.Iterable[t.Iterable[T]]]",
+        self: "Chain[t.Iterable[t.Tuple[T, T2]]]",
         iteratee: t.Union[
-            t.Callable[[T, T, int, t.List[T]], T2],
-            t.Callable[[T, T, int], T2],
-            t.Callable[[T, T], T2],
-            t.Callable[[T], T2],
+            t.Callable[[t.Union[T, T2, T3], t.Union[T, T2], int], T3],
+            t.Callable[[t.Union[T, T2, T3], t.Union[T, T2]], T3],
+            t.Callable[[t.Union[T, T2, T3]], T3],
         ],
-    ) -> "Chain[t.List[T2]]": ...
+    ) -> "Chain[t.List[T3]]": ...
+    @t.overload
+    def unzip_with(
+        self: "Chain[t.Iterable[t.Iterable[t.Any]]]",
+        iteratee: t.Union[
+            t.Callable[[t.Any, t.Any, int], T3],
+            t.Callable[[t.Any, t.Any], T3],
+            t.Callable[[t.Any], T3],
+        ],
+    ) -> "Chain[t.List[T3]]": ...
     @t.overload
     def unzip_with(
         self: "Chain[t.Iterable[t.Iterable[T]]]", iteratee: None = None
-    ) -> "Chain[t.List[t.List[T]]]": ...
+    ) -> "Chain[t.List[t.Tuple[T]]]": ...
     def unzip_with(self, iteratee=None):
         return self._wrap(pyd.unzip_with)(iteratee)
 
@@ -612,7 +638,11 @@ class AllFuncs:
     def xor_with(self, *lists, **kwargs):
         return self._wrap(pyd.xor_with)(*lists, **kwargs)
 
-    def zip_(self: "Chain[t.Iterable[T]]", *arrays: t.Iterable[T]) -> "Chain[t.List[t.List[T]]]":
+    @t.overload
+    def zip_(
+        self: "Chain[t.Iterable[t.Any]]", *arrays: t.Iterable[t.Any]
+    ) -> "Chain[t.List[t.Tuple[t.Any, ...]]]": ...
+    def zip_(self, *arrays):
         return self._wrap(pyd.zip_)(*arrays)
 
     zip = zip_
@@ -638,29 +668,32 @@ class AllFuncs:
     @t.overload
     def zip_with(
         self: "Chain[t.Iterable[T]]",
-        *arrays: t.Iterable[T],
+        array2: t.Iterable[T2],
+        *,
         iteratee: t.Union[
-            t.Callable[[T, T, int, t.List[T]], T2],
-            t.Callable[[T, T, int], T2],
-            t.Callable[[T, T], T2],
-            t.Callable[[T], T2],
+            t.Callable[[T, T2, int], T3], t.Callable[[T, T2], T3], t.Callable[[T], T3]
         ],
-    ) -> "Chain[t.List[T2]]": ...
+    ) -> "Chain[t.List[T3]]": ...
     @t.overload
     def zip_with(
-        self: "Chain[t.Iterable[T]]", *arrays: t.Iterable[T]
-    ) -> "Chain[t.List[t.List[T]]]": ...
+        self: "Chain[t.Iterable[t.Any]]",
+        *arrays: t.Iterable[t.Any],
+        iteratee: t.Union[
+            t.Callable[[t.Any, t.Any, int], T2],
+            t.Callable[[t.Any, t.Any], T2],
+            t.Callable[[t.Any], T2],
+        ],
+    ) -> "Chain[t.List[T2]]": ...
     @t.overload
     def zip_with(
-        self: "Chain[t.Union[t.Iterable[T], t.Callable[[T, T, int, t.List[T]], T2], t.Callable[[T, T, int], T2], t.Callable[[T, T], T2], t.Callable[[T], T2]]]",
+        self: "Chain[t.Union[t.Iterable[t.Any], t.Callable[[t.Any, t.Any, int], T2], t.Callable[[t.Any, t.Any], T2], t.Callable[[t.Any], T2]]]",
         *arrays: t.Union[
-            t.Iterable[T],
-            t.Callable[[T, T, int, t.List[T]], T2],
-            t.Callable[[T, T, int], T2],
-            t.Callable[[T, T], T2],
-            t.Callable[[T], T2],
+            t.Iterable[t.Any],
+            t.Callable[[t.Any, t.Any, int], T2],
+            t.Callable[[t.Any, t.Any], T2],
+            t.Callable[[t.Any], T2],
         ],
-    ) -> "Chain[t.List[t.Union[t.List[T], T2]]]": ...
+    ) -> "Chain[t.List[T2]]": ...
     def zip_with(self, *arrays, **kwargs):
         return self._wrap(pyd.zip_with)(*arrays, **kwargs)
 
@@ -2689,9 +2722,9 @@ class AllFuncs:
         return self._wrap(pyd.to_number)(precision)
 
     @t.overload
-    def to_pairs(self: "Chain[t.Mapping[T, T2]]") -> "Chain[t.List[t.List[t.Union[T, T2]]]]": ...
+    def to_pairs(self: "Chain[t.Mapping[T, T2]]") -> "Chain[t.List[t.Tuple[T, T2]]]": ...
     @t.overload
-    def to_pairs(self: "Chain[t.Iterable[T]]") -> "Chain[t.List[t.List[t.Union[int, T]]]]": ...
+    def to_pairs(self: "Chain[t.Iterable[T]]") -> "Chain[t.List[t.Tuple[int, T]]]": ...
     @t.overload
     def to_pairs(self: "Chain[t.Any]") -> "Chain[t.List]": ...
     def to_pairs(self):
diff --git a/src/pydash/objects.py b/src/pydash/objects.py
index d5a7c66..c325cb9 100644
--- a/src/pydash/objects.py
+++ b/src/pydash/objects.py
@@ -2114,12 +2114,12 @@ def to_number(obj: t.Any, precision: int = 0) -> t.Union[float, None]:
 
 
 @t.overload
-def to_pairs(obj: t.Mapping[T, T2]) -> t.List[t.List[t.Union[T, T2]]]:
+def to_pairs(obj: t.Mapping[T, T2]) -> t.List[t.Tuple[T, T2]]:
     ...
 
 
 @t.overload
-def to_pairs(obj: t.Iterable[T]) -> t.List[t.List[t.Union[int, T]]]:
+def to_pairs(obj: t.Iterable[T]) -> t.List[t.Tuple[int, T]]:
     ...
 
 
@@ -2130,28 +2130,31 @@ def to_pairs(obj: t.Any) -> t.List:
 
 def to_pairs(obj):
     """
-    Creates a two-dimensional list of an object's key-value pairs, i.e., ``[[key1, value1], [key2,
-    value2]]``.
+    Creates a list of tuples of an object's key-value pairs, i.e.,
+    ``[(key1, value1), (key2, value2)]``.
 
     Args:
         obj: Object to process.
 
     Returns:
-        Two dimensional list of object's key-value pairs.
+        List of tuples of the object's key-value pairs.
 
     Example:
 
         >>> to_pairs([1, 2, 3, 4])
-        [[0, 1], [1, 2], [2, 3], [3, 4]]
+        [(0, 1), (1, 2), (2, 3), (3, 4)]
         >>> to_pairs({"a": 1})
-        [['a', 1]]
+        [('a', 1)]
 
     .. versionadded:: 1.0.0
 
     .. versionchanged:: 4.0.0
         Renamed from ``pairs`` to ``to_pairs``.
+
+    .. versionchanged:: 8.0.0
+        Returning list of tuples instead of list of lists.
     """
-    return [[key, value] for key, value in iterator(obj)]
+    return [(key, value) for key, value in iterator(obj)]
 
 
 def to_string(obj: t.Any) -> str:


Test patch:
diff --git a/tests/pytest_mypy_testing/test_arrays.py b/tests/pytest_mypy_testing/test_arrays.py
index 2e30951..d7d6cb3 100644
--- a/tests/pytest_mypy_testing/test_arrays.py
+++ b/tests/pytest_mypy_testing/test_arrays.py
@@ -408,8 +408,7 @@ def test_mypy_unshift() -> None:
 
 @pytest.mark.mypy_testing
 def test_mypy_unzip() -> None:
-    reveal_type(_.unzip([[1, 4, 7], [2, 5, 8], [3, 6, 9]]))  # R: builtins.list[builtins.list[builtins.int]]
-    reveal_type(_.unzip([(1, 4, 7), (2, 5, 8), (3, 6, 9)]))  # R: builtins.list[builtins.list[builtins.int]]
+    reveal_type(_.unzip([(1, 4, 7), (2, 5, 8), (3, 6, 9)]))  # R: builtins.list[Tuple[builtins.int, builtins.int, builtins.int]]
 
 
 @pytest.mark.mypy_testing
@@ -447,7 +446,8 @@ def test_mypy_xor_with() -> None:
 
 @pytest.mark.mypy_testing
 def test_mypy_zip_() -> None:
-    reveal_type(_.zip_([1, 2, 3], [4, 5, 6], [7, 8, 9]))  # R: builtins.list[builtins.list[builtins.int]]
+    reveal_type(_.zip_([1, 2, 3], [4, 5, 6], [7, 8, 9]))  # R: builtins.list[Tuple[builtins.int, builtins.int, builtins.int]]
+    reveal_type(_.zip_([1, 2, 3], ["one", "two", "three"]))  # R: builtins.list[Tuple[builtins.int, builtins.str]]
 
 
 @pytest.mark.mypy_testing
@@ -469,5 +469,10 @@ def test_mypy_zip_with() -> None:
     def add(x: int, y: int) -> int:
         return x + y
 
-    reveal_type(_.zip_with([1, 2], [10, 20], [100, 200], add))  # R: builtins.list[Union[builtins.list[builtins.int], builtins.int]]
+    reveal_type(_.zip_with([1, 2], [10, 20], add))  # R: builtins.list[builtins.int]
     reveal_type(_.zip_with([1, 2], [10, 20], [100, 200], iteratee=add))  # R: builtins.list[builtins.int]
+
+    def more_hello(s: str, n: int) -> str:
+        return s * n
+
+    reveal_type(_.zip_with(["hello", "hello", "hello"], [1, 2, 3], iteratee=more_hello))  # R: builtins.list[builtins.str]
diff --git a/tests/pytest_mypy_testing/test_objects.py b/tests/pytest_mypy_testing/test_objects.py
index b4df527..28645e0 100644
--- a/tests/pytest_mypy_testing/test_objects.py
+++ b/tests/pytest_mypy_testing/test_objects.py
@@ -298,8 +298,8 @@ def test_mypy_to_number() -> None:
 
 @pytest.mark.mypy_testing
 def test_mypy_to_pairs() -> None:
-    reveal_type(_.to_pairs([1, 2, 3, 4]))  # R: builtins.list[builtins.list[builtins.int]]
-    reveal_type(_.to_pairs({'a': 1}))  # R: builtins.list[builtins.list[Union[builtins.str, builtins.int]]]
+    reveal_type(_.to_pairs([1, 2, 3, 4]))  # R: builtins.list[Tuple[builtins.int, builtins.int]]
+    reveal_type(_.to_pairs({'a': 1}))  # R: builtins.list[Tuple[builtins.str, builtins.int]]
     reveal_type(_.to_pairs(MyClass()))  # R: builtins.list[Any]
 
 
diff --git a/tests/test_arrays.py b/tests/test_arrays.py
index cdf6217..a0070cf 100644
--- a/tests/test_arrays.py
+++ b/tests/test_arrays.py
@@ -853,8 +853,8 @@ def test_unshift(case, expected):
     "case,expected",
     [
         (
-            [["moe", 30, True], ["larry", 40, False], ["curly", 35, True]],
-            [["moe", "larry", "curly"], [30, 40, 35], [True, False, True]],
+            [("moe", 30, True), ("larry", 40, False), ("curly", 35, True)],
+            [("moe", "larry", "curly"), (30, 40, 35), (True, False, True)],
         )
     ],
 )
@@ -866,7 +866,7 @@ def test_unzip(case, expected):
     "case,expected",
     [
         (([],), []),
-        (([[1, 10, 100], [2, 20, 200]],), [[1, 2], [10, 20], [100, 200]]),
+        (([[1, 10, 100], [2, 20, 200]],), [(1, 2), (10, 20), (100, 200)]),
         (([[2, 4, 6], [2, 2, 2]], _.power), [4, 16, 36]),
     ],
 )
@@ -902,7 +902,7 @@ def test_xor_with(case, expected):
     [
         (
             (["moe", "larry", "curly"], [30, 40, 35], [True, False, True]),
-            [["moe", 30, True], ["larry", 40, False], ["curly", 35, True]],
+            [("moe", 30, True), ("larry", 40, False), ("curly", 35, True)],
         )
     ],
 )
@@ -935,7 +935,7 @@ def test_zip_object_deep(case, expected):
 @parametrize(
     "case,expected",
     [
-        (([1, 2],), [[1], [2]]),
+        (([1, 2],), [(1,), (2,)]),
         (([1, 2], [3, 4], _.add), [4, 6]),
     ],
 )
diff --git a/tests/test_objects.py b/tests/test_objects.py
index d3328cf..d0f0ea4 100644
--- a/tests/test_objects.py
+++ b/tests/test_objects.py
@@ -847,8 +847,8 @@ def test_to_number(case, expected):
 @parametrize(
     "case,expected",
     [
-        ({"a": 1, "b": 2, "c": 3}, [["a", 1], ["b", 2], ["c", 3]]),
-        ([1, 2, 3], [[0, 1], [1, 2], [2, 3]]),
+        ({"a": 1, "b": 2, "c": 3}, [("a", 1), ("b", 2), ("c", 3)]),
+        ([1, 2, 3], [(0, 1), (1, 2), (2, 3)]),
     ],
 )
 def test_to_pairs(case, expected):