Repository: sympy/sympy
Pull request: printing: Remove curly braces from subscripted variables in python() and pycode()
<!-- Your title above should be a short description of what
was changed. Do not include the issue number in the title. -->

#### References to other Issues or PRs
<!-- If this pull request fixes an issue, write "Fixes #NNNN" in that exact
format, e.g. "Fixes #1234" (see
https://tinyurl.com/auto-closing for more information). Also, please
write a comment on that issue linking back to this pull request once it is
open. -->
Fixes #20762 

#### Brief description of what is fixed or changed
Added handling for subscripted variables in `python` and `pycode`. Subscripted variables only become an issue when using a parsed latex expression, since latex wraps them in curly braces. `python` and `pycode` were creating variable names with curly braces in them, resulting in syntax errors. Added a simple check and modification in `python`, but redefined inherited `_print_Symbol` in `pycode`'s `PythonCodePrinter`.

#### Other comments


#### Release Notes

<!-- Write the release notes for this release below. See
https://github.com/sympy/sympy/wiki/Writing-Release-Notes for more information
on how to write release notes. The bot will check your release notes
automatically to see if they are formatted correctly. -->

<!-- BEGIN RELEASE NOTES -->
* printing
    * Remove curly braces from subscripted variables in python() and pycode()
<!-- END RELEASE NOTES -->

Problem statement:
pycode of latex expression with subscript leads to syntax error
Hey guys, while generating python code from a simple latex expression works

```python
expr = parse_latex(r'a \cdot b')
print(sympy.pycode(expr))
`>>> a*b
print(sympy.python(expr))
>>> a = Symbol('a')
>>> b = Symbol('b')
>>> e = a*b
```
for anything with a subscript the code generated by `pycode` or `.python` will lead to a syntax error due to the non-compliant variable name.

```python
expr = parse_latex(r'a_b \cdot b')
print(sympy.pycode(expr))
>>> a_{b}*b
print(sympy.python(expr))
>>> a_{b} = Symbol('a_{b}')  # this will generate a syntax error
>>> b = Symbol('b')
>>> e = a_{b}*b  # and this as well 
```

Cheers and thanks for the great work!

-------
versions
python = "3.9.1"
sympy = '1.7.1'
ANTLR runtime = "4.9.1"

Hints from issue discussion:
Would something like this fix the issue in `python`?

```diff
diff --git a/sympy/printing/python.py b/sympy/printing/python.py
index ac6d95a06c..f60719b497 100644
--- a/sympy/printing/python.py
+++ b/sympy/printing/python.py
@@ -51,7 +51,13 @@ def python(expr, **settings):
     # Returning found symbols and functions
     renamings = {}
     for symbolname in printer.symbols:
-        newsymbolname = symbolname
+        # Remove curly braces from subscripted variables
+        if '{' in symbolname:
+            newsymbolname = ''.join([s if s != '{' and s != '}' else '' for s in symbolname])
+            renamings[sympy.Symbol(symbolname)] = newsymbolname
+        else:
+            newsymbolname = symbolname
+
         # Escape symbol names that are reserved python keywords
         if kw.iskeyword(newsymbolname):
             while True:
```

Commit message: Add tests

Patch to review:
diff --git a/sympy/printing/pycode.py b/sympy/printing/pycode.py
index b7315a4c559c..7c7898612407 100644
--- a/sympy/printing/pycode.py
+++ b/sympy/printing/pycode.py
@@ -456,6 +456,21 @@ def _print_frac(self, expr):
         from sympy import Mod
         return self._print_Mod(Mod(expr.args[0], 1))
 
+    def _print_Symbol(self, expr):
+
+        name = super()._print_Symbol(expr)
+
+        if name in self.reserved_words:
+            if self._settings['error_on_reserved']:
+                msg = ('This expression includes the symbol "{}" which is a '
+                       'reserved keyword in this language.')
+                raise ValueError(msg.format(name))
+            return name + self._settings['reserved_word_suffix']
+        elif '{' in name:   # Remove curly braces from subscripted variables
+            return ''.join([s if s != '{' and s != '}' else '' for s in name])
+        else:
+            return name
+
     _print_lowergamma = CodePrinter._print_not_supported
     _print_uppergamma = CodePrinter._print_not_supported
     _print_fresnelc = CodePrinter._print_not_supported
diff --git a/sympy/printing/python.py b/sympy/printing/python.py
index ac6d95a06ca5..f60719b49778 100644
--- a/sympy/printing/python.py
+++ b/sympy/printing/python.py
@@ -51,7 +51,13 @@ def python(expr, **settings):
     # Returning found symbols and functions
     renamings = {}
     for symbolname in printer.symbols:
-        newsymbolname = symbolname
+        # Remove curly braces from subscripted variables
+        if '{' in symbolname:
+            newsymbolname = ''.join([s if s != '{' and s != '}' else '' for s in symbolname])
+            renamings[sympy.Symbol(symbolname)] = newsymbolname
+        else:
+            newsymbolname = symbolname
+
         # Escape symbol names that are reserved python keywords
         if kw.iskeyword(newsymbolname):
             while True:
diff --git a/sympy/printing/tests/test_pycode.py b/sympy/printing/tests/test_pycode.py
index a8f6f97a8fa2..362bdd27680b 100644
--- a/sympy/printing/tests/test_pycode.py
+++ b/sympy/printing/tests/test_pycode.py
@@ -18,6 +18,7 @@
 from sympy.testing.pytest import skip
 from sympy.external import import_module
 from sympy.functions.special.gamma_functions import loggamma
+from sympy.parsing.latex import parse_latex
 
 x, y, z = symbols('x y z')
 p = IndexedBase("p")
@@ -166,6 +167,11 @@ def test_pycode_reserved_words():
     py_str = pycode(s1 + s2)
     assert py_str in ('else_ + if_', 'if_ + else_')
 
+def test_issue_20762():
+    # Make sure pycode removes curly braces from subscripted variables
+    expr = parse_latex(r'a_b \cdot b')
+    assert pycode(expr) == 'a_b*b'
+
 
 def test_sqrt():
     prntr = PythonCodePrinter()
diff --git a/sympy/printing/tests/test_python.py b/sympy/printing/tests/test_python.py
index 89a2e48a3b87..7071d56c2094 100644
--- a/sympy/printing/tests/test_python.py
+++ b/sympy/printing/tests/test_python.py
@@ -6,6 +6,8 @@
 
 from sympy.testing.pytest import raises, XFAIL
 
+from sympy.parsing.latex import parse_latex
+
 x, y = symbols('x,y')
 th = Symbol('theta')
 ph = Symbol('phi')
@@ -183,6 +185,11 @@ def test_python_limits():
     assert python(limit(x, x, oo)) == 'e = oo'
     assert python(limit(x**2, x, 0)) == 'e = 0'
 
+def test_issue_20762():
+    # Make sure python removes curly braces from subscripted variables
+    expr = parse_latex(r'a_b \cdot b')
+    assert python(expr) == "a_b = Symbol('a_{b}')\nb = Symbol('b')\ne = a_b*b"
+
 
 def test_settings():
     raises(TypeError, lambda: python(x, method="garbage"))


Code review comments:
diff_hunk: @@ -51,7 +51,13 @@ def python(expr, **settings):
     # Returning found symbols and functions
     renamings = {}
     for symbolname in printer.symbols:
-        newsymbolname = symbolname
+        # Remove curly braces from subscripted variables
+        if '{' in symbolname:
+            newsymbolname = ''.join([s if s != '{' and s != '}' else '' for s in symbolname])
path: sympy/printing/python.py
text: @user1:
What about `newsymbolname = symbolname.replace('{', '').replace('}', '')`?