Repository: sympy/sympy

Issue:
Tuple using sympify instead of _sympify incorrectly supports lists
Currently, `Tuple` supports the keyword `sympify` (`True` by default). However, this uses `sympify` rather than `_sympify`. As a result, `lists` appear to be supported as arguments to `Tuple` but this may result in unexpected behaviour since the resulting `Tuple` is now in principle mutable. Instead, I suggest to use `_sympify` over `sympify` and check for any `list` argument and convert those using `Tuple(*list_arg)`. 

Discussion:

Patch:
diff --git a/sympy/codegen/abstract_nodes.py b/sympy/codegen/abstract_nodes.py
new file mode 100644
index 0000000000..eb5da05fc0
--- /dev/null
+++ b/sympy/codegen/abstract_nodes.py
@@ -0,0 +1,15 @@
+"""This module provides containers for python objects that are valid
+printing targets but are not a subclass of SymPy's Printable.
+"""
+
+
+from sympy.core.containers import Tuple
+
+
+class List(Tuple):
+    """Represents a (frozen) (Python) list (for code printing purposes)."""
+    def __eq__(self, other):
+        if isinstance(other, list):
+            return self == List(*other)
+        else:
+            return self.args == other
diff --git a/sympy/codegen/pynodes.py b/sympy/codegen/pynodes.py
index 2fe93c6e25..407fbde3ae 100644
--- a/sympy/codegen/pynodes.py
+++ b/sympy/codegen/pynodes.py
@@ -1,10 +1,4 @@
-from sympy.core import Tuple
+from .abstract_nodes import List as AbstractList
 
-
-class List(Tuple):
-    """Represents a (frozen) (Python) list (for code printing purposes)."""
-    def __eq__(self, other):
-        if isinstance(other, list):
-            return self == List(*other)
-        else:
-            return self.args == other
+class List(AbstractList):
+    pass
diff --git a/sympy/core/function.py b/sympy/core/function.py
index bb4ae3a6e0..1282fd5547 100644
--- a/sympy/core/function.py
+++ b/sympy/core/function.py
@@ -1269,20 +1269,18 @@ def __new__(cls, expr, *variables, **kwargs):
                         expression, the variable(s) of differentiation
                         must be supplied to differentiate %s''' % expr))
 
-        # Standardize the variables by sympifying them:
-        variables = list(sympify(variables))
-
         # Split the list of variables into a list of the variables we are diff
         # wrt, where each element of the list has the form (s, count) where
         # s is the entity to diff wrt and count is the order of the
         # derivative.
         variable_count = []
         array_likes = (tuple, list, Tuple)
+        integer_likes = (int, Integer)
 
         from sympy.tensor.array import Array, NDimArray
 
         for i, v in enumerate(variables):
-            if isinstance(v, Integer):
+            if isinstance(v, integer_likes):
                 if i == 0:
                     raise ValueError("First variable cannot be a number: %i" % v)
                 count = v
diff --git a/sympy/printing/codeprinter.py b/sympy/printing/codeprinter.py
index 8c2f803ca3..98f8615621 100644
--- a/sympy/printing/codeprinter.py
+++ b/sympy/printing/codeprinter.py
@@ -36,8 +36,10 @@ class AssignmentError(Exception):
 
 def _convert_python_lists(arg):
     if isinstance(arg, list):
-        from sympy.codegen.pynodes import List
+        from sympy.codegen.abstract_nodes import List
         return List(*(_convert_python_lists(e) for e in arg))
+    elif isinstance(arg, tuple):
+        return tuple(_convert_python_lists(e) for e in arg)
     else:
         return arg
 
@@ -134,8 +136,9 @@ def _handle_assign_to(expr, assign_to):
                         type(self).__name__, type(assign_to)))
             return Assignment(assign_to, expr)
 
-        expr = _handle_assign_to(expr, assign_to)
         expr = _convert_python_lists(expr)
+        expr = _handle_assign_to(expr, assign_to)
+
         # Remove re(...) nodes due to UnevaluatedExpr.is_real always is None:
         expr = self._handle_UnevaluatedExpr(expr)
 
diff --git a/sympy/printing/tableform.py b/sympy/printing/tableform.py
index c102ffe7f5..8d39285a47 100644
--- a/sympy/printing/tableform.py
+++ b/sympy/printing/tableform.py
@@ -144,7 +144,7 @@ def __init__(self, data, **kwarg):
                         lj = Symbol(str(lj))
                 line[j] = lj
             data[i] = line
-        _lines = Tuple(*data)
+        _lines = Tuple(*[Tuple(*d) for d in data])
 
         headings = kwarg.get("headings", [None, None])
         if headings == "automatic":
diff --git a/sympy/stats/stochastic_process_types.py b/sympy/stats/stochastic_process_types.py
index ab5f934d0d..a43003aaa0 100644
--- a/sympy/stats/stochastic_process_types.py
+++ b/sympy/stats/stochastic_process_types.py
@@ -1091,9 +1091,8 @@ def communication_classes(self) -> tList[tTuple[tList[Basic], Boolean, Integer]]
             # end breadth-first search
 
         # convert back to the user's state names
-        classes = [[self._state_index[i] for i in class_] for class_ in classes]
-
-        return sympify(list(zip(classes, recurrence, periods)))
+        classes = [[_sympify(self._state_index[i]) for i in class_] for class_ in classes]
+        return list(zip(classes, recurrence, map(Integer,periods)))
 
     def fundamental_matrix(self):
         """
diff --git a/sympy/tensor/array/array_comprehension.py b/sympy/tensor/array/array_comprehension.py
index 9498ea7f9f..329696bef2 100644
--- a/sympy/tensor/array/array_comprehension.py
+++ b/sympy/tensor/array/array_comprehension.py
@@ -1,7 +1,7 @@
 import functools, itertools
-from sympy.core.sympify import sympify
+from sympy.core.sympify import _sympify, sympify
 from sympy.core.expr import Expr
-from sympy.core import Basic
+from sympy.core import Basic, Tuple
 from sympy.tensor.array import ImmutableDenseNDimArray
 from sympy.core.symbol import Symbol
 from sympy.core.numbers import Integer
@@ -225,8 +225,18 @@ def __len__(self):
 
     @classmethod
     def _check_limits_validity(cls, function, limits):
-        limits = sympify(limits)
+        #limits = sympify(limits)
+        new_limits = []
         for var, inf, sup in limits:
+            var = _sympify(var)
+            inf = _sympify(inf)
+            #since this is stored as an argument, it should be
+            #a Tuple
+            if isinstance(sup, list):
+                sup = Tuple(*sup)
+            else:
+                sup = _sympify(sup)
+            new_limits.append(Tuple(var, inf, sup))
             if any((not isinstance(i, Expr)) or i.atoms(Symbol, Integer) != i.atoms()
                                                                 for i in [inf, sup]):
                 raise TypeError('Bounds should be an Expression(combination of Integer and Symbol)')
@@ -234,7 +244,7 @@ def _check_limits_validity(cls, function, limits):
                 raise ValueError('Lower bound should be inferior to upper bound')
             if var in inf.free_symbols or var in sup.free_symbols:
                 raise ValueError('Variable should not be part of its bounds')
-        return limits
+        return new_limits
 
     @classmethod
     def _calculate_shape_from_limits(cls, limits):


Test patch:
diff --git a/sympy/core/tests/test_args.py b/sympy/core/tests/test_args.py
index 29a727d55d..60cfabd584 100644
--- a/sympy/core/tests/test_args.py
+++ b/sympy/core/tests/test_args.py
@@ -491,6 +491,9 @@ def test_sympy__codegen__scipy_nodes__cosm1():
     from sympy.codegen.scipy_nodes import cosm1
     assert _test_args(cosm1(x))
 
+def test_sympy__codegen__abstract_nodes__List():
+    from sympy.codegen.abstract_nodes import List
+    assert _test_args(List(1, 2, 3))
 
 @XFAIL
 def test_sympy__combinatorics__graycode__GrayCode():
diff --git a/sympy/simplify/tests/test_cse.py b/sympy/simplify/tests/test_cse.py
index efb39bc2d3..eb9cc23131 100644
--- a/sympy/simplify/tests/test_cse.py
+++ b/sympy/simplify/tests/test_cse.py
@@ -584,8 +584,10 @@ def test_cse_list():
     assert _cse(x) == ([], x)
     assert _cse('x') == ([], 'x')
     it = [x]
-    for c in (list, tuple, set, Tuple):
+    for c in (list, tuple, set):
         assert _cse(c(it)) == ([], c(it))
+    #Tuple works different from tuple:
+    assert _cse(Tuple(*it)) == ([], Tuple(*it))
     d = {x: 1}
     assert _cse(d) == ([], d)