diff --git a/py/obj.c b/py/obj.c
index f80d1a977233470b566581d4308d787413c2e0b3..95052d16d2730f00dfaf871cb9e3e426e153e48d 100644
--- a/py/obj.c
+++ b/py/obj.c
@@ -62,7 +62,7 @@ void mp_obj_print_exception(mp_obj_t exc) {
             }
         }
     }
-    mp_obj_print(exc, PRINT_REPR);
+    mp_obj_print(exc, PRINT_EXC);
     printf("\n");
 }
 
diff --git a/py/obj.h b/py/obj.h
index a34d5407afc974d78f9eefe38e4caa9a0bebd35f..952187e4645faa8d18bb3e58c442328138bd1c03 100644
--- a/py/obj.h
+++ b/py/obj.h
@@ -141,7 +141,9 @@ typedef mp_obj_t (*mp_fun_var_t)(uint n, const mp_obj_t *);
 typedef mp_obj_t (*mp_fun_kw_t)(uint n, const mp_obj_t *, mp_map_t *);
 
 typedef enum {
-    PRINT_STR, PRINT_REPR
+    PRINT_STR,
+    PRINT_REPR,
+    PRINT_EXC, // Special format for printing exception in unhandled exception message
 } mp_print_kind_t;
 
 typedef void (*mp_print_fun_t)(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o, mp_print_kind_t kind);
diff --git a/py/objexcept.c b/py/objexcept.c
index 7dd5c7ac39e8659c340bfa4d7252ffe85383af7e..0efc21d3606924b38c668f9010dcc894dd46a916 100644
--- a/py/objexcept.c
+++ b/py/objexcept.c
@@ -11,42 +11,34 @@
 #include "runtime.h"
 #include "runtime0.h"
 
-// This is unified class for C-level and Python-level exceptions
-// Python-level exceptions have empty ->msg and all arguments are in
-// args tuple. C-level exceptions likely have ->msg set, and args is empty.
 typedef struct _mp_obj_exception_t {
     mp_obj_base_t base;
     mp_obj_t traceback; // a list object, holding (file,line,block) as numbers (not Python objects); a hack for now
-    vstr_t *msg;
     mp_obj_tuple_t args;
 } mp_obj_exception_t;
 
 // Instance of GeneratorExit exception - needed by generator.close()
 // This would belong to objgenerator.c, but to keep mp_obj_exception_t
 // definition module-private so far, have it here.
-const mp_obj_exception_t mp_const_GeneratorExit_obj = {{&mp_type_GeneratorExit}, MP_OBJ_NULL, NULL, {{&mp_type_tuple}, 0}};
+const mp_obj_exception_t mp_const_GeneratorExit_obj = {{&mp_type_GeneratorExit}, MP_OBJ_NULL, {{&mp_type_tuple}, 0}};
 
 STATIC void mp_obj_exception_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind) {
     mp_obj_exception_t *o = o_in;
-    if (o->msg != NULL) {
-        print(env, "%s: %s", qstr_str(o->base.type->name), vstr_str(o->msg));
-    } else {
-        // Yes, that's how CPython has it
-        // TODO now that exceptions are classes and instances, I think this needs to be changed to match CPython
-        if (kind == PRINT_REPR) {
-            print(env, "%s", qstr_str(o->base.type->name));
-        }
-        if (kind == PRINT_STR) {
-            if (o->args.len == 0) {
-                print(env, "");
-                return;
-            } else if (o->args.len == 1) {
-                mp_obj_print_helper(print, env, o->args.items[0], PRINT_STR);
-                return;
-            }
+    if (kind == PRINT_REPR) {
+        print(env, "%s", qstr_str(o->base.type->name));
+    } else if (kind == PRINT_EXC) {
+        print(env, "%s: ", qstr_str(o->base.type->name));
+    }
+    if (kind == PRINT_STR || kind == PRINT_EXC) {
+        if (o->args.len == 0) {
+            print(env, "");
+            return;
+        } else if (o->args.len == 1) {
+            mp_obj_print_helper(print, env, o->args.items[0], PRINT_STR);
+            return;
         }
-        tuple_print(print, env, &o->args, kind);
     }
+    tuple_print(print, env, &o->args, kind);
 }
 
 STATIC mp_obj_t mp_obj_exception_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_obj_t *args) {
@@ -59,7 +51,6 @@ STATIC mp_obj_t mp_obj_exception_make_new(mp_obj_t type_in, uint n_args, uint n_
     mp_obj_exception_t *o = m_new_obj_var(mp_obj_exception_t, mp_obj_t, n_args);
     o->base.type = type;
     o->traceback = MP_OBJ_NULL;
-    o->msg = NULL;
     o->args.base.type = &mp_type_tuple;
     o->args.len = n_args;
     memcpy(o->args.items, args, n_args * sizeof(mp_obj_t));
@@ -185,7 +176,7 @@ MP_DEFINE_EXCEPTION(Exception, BaseException)
     */
 
 mp_obj_t mp_obj_new_exception(const mp_obj_type_t *exc_type) {
-    return mp_obj_new_exception_msg_varg(exc_type, NULL);
+    return mp_obj_new_exception_args(exc_type, 0, NULL);
 }
 
 mp_obj_t mp_obj_new_exception_args(const mp_obj_type_t *exc_type, uint n_args, const mp_obj_t *args) {
@@ -202,22 +193,25 @@ mp_obj_t mp_obj_new_exception_msg_varg(const mp_obj_type_t *exc_type, const char
     assert(exc_type->make_new == mp_obj_exception_make_new);
 
     // make exception object
-    mp_obj_exception_t *o = m_new_obj_var(mp_obj_exception_t, mp_obj_t, 0);
+    mp_obj_exception_t *o = m_new_obj_var(mp_obj_exception_t, mp_obj_t, 1);
     o->base.type = exc_type;
     o->traceback = MP_OBJ_NULL;
     o->args.base.type = &mp_type_tuple;
-    o->args.len = 0;
+    o->args.len = 1;
 
     if (fmt == NULL) {
         // no message
-        o->msg = NULL;
+        assert(0);
     } else {
-        // render exception message
-        o->msg = vstr_new();
+        // render exception message and store as .args[0]
+        // TODO: optimize bufferbloat
+        vstr_t *vstr = vstr_new();
         va_list ap;
         va_start(ap, fmt);
-        vstr_vprintf(o->msg, fmt, ap);
+        vstr_vprintf(vstr, fmt, ap);
         va_end(ap);
+        o->args.items[0] = mp_obj_new_str((byte*)vstr->buf, vstr->len, false);
+        vstr_free(vstr);
     }
 
     return o;