Repository: sympy/sympy

Issue:
mathematica_code has unexpected side effect on user_functions argument
The function `mathematica_code` modifies its optional argument `user_functions`. This makes subsequent calls fail to work as expected.

Here is an example:

```
import sympy
z = sympy.symbols("z")
some_function = sympy.symbols("some_function", cls=sympy.Function)
my_user_functions = {"some_function": "SomeFunction"}
print(sympy.mathematica_code(some_function(z), user_functions=my_user_functions))
print(sympy.mathematica_code(some_function(z), user_functions=my_user_functions))
print(my_user_functions)
```

It prints

```
SomeFunction[z]
some_function[z]
{'some_function': [(<function MCodePrinter.__init__.<locals>.<lambda> at 0x10300ec80>, 'SomeFunction')]}
```

The first line is what is expected. 
The second line should be identical to the first one. 
The third line should be `{'some_function': 'SomeFunction'}`.

At first sight, this could be fixed by replacing `userfuncs = settings.get('user_functions', {})`
by `userfuncs = copy.copy(settings.get('user_functions', {}))` [in `MCodePrinter.__init__`](https://github.com/sympy/sympy/blob/4f9d65448af6e1f1c794d1bdeaec4abfb72796f1/sympy/printing/mathematica.py#L62) (with an additional `import copy`).

It is also not clear why the interface does not allow to (optionally) pass an object of the form {"some_function": [(some_cond, "SomeFunction"), ...]} to `user_functions`. The current `MCodePrinter` appears to never use the mechanism, but it might be useful to have it exposed.

(This was tested with `sympy.__version__ == '1.3'`.)

Discussion:

Patch:
diff --git a/sympy/printing/mathematica.py b/sympy/printing/mathematica.py
index c3a2df6ce0..0d639936ab 100644
--- a/sympy/printing/mathematica.py
+++ b/sympy/printing/mathematica.py
@@ -59,11 +59,11 @@ def __init__(self, settings={}):
         """Register function mappings supplied by user"""
         CodePrinter.__init__(self, settings)
         self.known_functions = dict(known_functions)
-        userfuncs = settings.get('user_functions', {})
+        userfuncs = settings.get('user_functions', {}).copy()
         for k, v in userfuncs.items():
             if not isinstance(v, list):
                 userfuncs[k] = [(lambda *x: True, v)]
-                self.known_functions.update(userfuncs)
+        self.known_functions.update(userfuncs)
 
     def _format_code(self, lines):
         return lines


Test patch:
diff --git a/sympy/printing/tests/test_mathematica.py b/sympy/printing/tests/test_mathematica.py
index ad7658b2fd..4e0299051e 100644
--- a/sympy/printing/tests/test_mathematica.py
+++ b/sympy/printing/tests/test_mathematica.py
@@ -196,3 +196,24 @@ def test_comment():
     from sympy.printing.mathematica import MCodePrinter
     assert MCodePrinter()._get_comment("Hello World") == \
         "(* Hello World *)"
+
+def test_userfuncs():
+    # Dictionary mutation test
+    some_function = symbols("some_function", cls=Function)
+    my_user_functions = {"some_function": "SomeFunction"}
+    assert mcode(
+        some_function(z),
+        user_functions=my_user_functions) == \
+        'SomeFunction[z]'
+    assert mcode(
+        some_function(z),
+        user_functions=my_user_functions) == \
+        'SomeFunction[z]'
+
+    # List argument test
+    my_user_functions = \
+        {"some_function": [(lambda x: True, "SomeOtherFunction")]}
+    assert mcode(
+        some_function(z),
+        user_functions=my_user_functions) == \
+        'SomeOtherFunction[z]'