Revert "Revert "THRIFT-4002: Make generated exception classes immutable by default""

This reverts commit 1234ddf8a5c98d5d700c82e087f04725170ad581.
diff --git a/test/DebugProtoTest.thrift b/test/DebugProtoTest.thrift
index de47ea7..1ab0f6a 100644
--- a/test/DebugProtoTest.thrift
+++ b/test/DebugProtoTest.thrift
@@ -241,6 +241,10 @@
   2: map<string, string> map_field;
 }
 
+exception MutableException {
+  1: string msg;
+} (python.immutable = "false")
+
 service ServiceForExceptionWithAMap {
   void methodThatThrowsAnException() throws (1: ExceptionWithAMap xwamap);
 }
diff --git a/test/py.tornado/test_suite.py b/test/py.tornado/test_suite.py
index 447fde6..0ee0a9b 100755
--- a/test/py.tornado/test_suite.py
+++ b/test/py.tornado/test_suite.py
@@ -82,10 +82,7 @@
 
     def testException(self, s):
         if s == 'Xception':
-            x = Xception()
-            x.errorCode = 1001
-            x.message = s
-            raise x
+            raise Xception(1001, s)
         elif s == 'throw_undeclared':
             raise ValueError('testing undeclared exception')
 
diff --git a/test/py.twisted/test_suite.py b/test/py.twisted/test_suite.py
index 02eb7f1..6e04493 100755
--- a/test/py.twisted/test_suite.py
+++ b/test/py.twisted/test_suite.py
@@ -76,10 +76,7 @@
 
     def testException(self, s):
         if s == 'Xception':
-            x = Xception()
-            x.errorCode = 1001
-            x.message = s
-            raise x
+            raise Xception(1001, s)
         elif s == "throw_undeclared":
             raise ValueError("foo")
 
diff --git a/test/py/TestFrozen.py b/test/py/TestFrozen.py
index 6d2595c..ce7425f 100755
--- a/test/py/TestFrozen.py
+++ b/test/py/TestFrozen.py
@@ -19,7 +19,9 @@
 # under the License.
 #
 
+from DebugProtoTest import Srv
 from DebugProtoTest.ttypes import CompactProtoTestStruct, Empty, Wrapper
+from DebugProtoTest.ttypes import ExceptionWithAMap, MutableException
 from thrift.Thrift import TFrozenDict
 from thrift.transport import TTransport
 from thrift.protocol import TBinaryProtocol, TCompactProtocol
@@ -94,6 +96,21 @@
         x2 = self._roundtrip(x, Wrapper)
         self.assertEqual(x2.foo, Empty())
 
+    def test_frozen_exception(self):
+        exc = ExceptionWithAMap(blah='foo')
+        with self.assertRaises(TypeError):
+            exc.blah = 'bar'
+        mutexc = MutableException(msg='foo')
+        mutexc.msg = 'bar'
+        self.assertEqual(mutexc.msg, 'bar')
+
+    def test_frozen_exception_serialization(self):
+        result = Srv.declaredExceptionMethod_result(
+            xwamap=ExceptionWithAMap(blah="error"))
+        deserialized = self._roundtrip(
+            result, Srv.declaredExceptionMethod_result())
+        self.assertEqual(result, deserialized)
+
 
 class TestFrozen(TestFrozenBase):
     def protocol(self, trans):