OSDN Git Service

Let the name of wrapped functions appear in tracebacks.
authorSimon Forman <sforman@hushmail.com>
Sat, 21 Apr 2018 22:37:19 +0000 (15:37 -0700)
committerSimon Forman <sforman@hushmail.com>
Sat, 21 Apr 2018 22:37:19 +0000 (15:37 -0700)
joy/library.py
joy/utils/brutal_hackery.py [new file with mode: 0644]

index 9d6d6a3..4b62c71 100644 (file)
@@ -29,6 +29,7 @@ import operator, math
 
 from .parser import text_to_expression, Symbol
 from .utils.stack import list_to_stack, iter_stack, pick, pushback
+from .utils.brutal_hackery import rename_code_object
 
 
 _dictionary = {}
@@ -167,6 +168,7 @@ def SimpleFunctionWrapper(f):
   '''
   @FunctionWrapper
   @wraps(f)
+  @rename_code_object(f.__name__)
   def inner(stack, expression, dictionary):
     return f(stack), expression, dictionary
   return inner
@@ -178,6 +180,7 @@ def BinaryBuiltinWrapper(f):
   '''
   @FunctionWrapper
   @wraps(f)
+  @rename_code_object(f.__name__)
   def inner(stack, expression, dictionary):
     (a, (b, stack)) = stack
     result = f(b, a)
@@ -191,6 +194,7 @@ def UnaryBuiltinWrapper(f):
   '''
   @FunctionWrapper
   @wraps(f)
+  @rename_code_object(f.__name__)
   def inner(stack, expression, dictionary):
     (a, stack) = stack
     result = f(a)
diff --git a/joy/utils/brutal_hackery.py b/joy/utils/brutal_hackery.py
new file mode 100644 (file)
index 0000000..0854691
--- /dev/null
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+#
+#    Copyright © 2018 Simon Forman
+#
+#    This file is part of Joypy
+#
+#    Joypy is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    Joypy is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with Joypy.  If not see <http://www.gnu.org/licenses/>.
+#
+'''
+I really want tracebacks to show which function was being executed when
+an error in the wrapper function happens.  In order to do that, you have
+to do this (the function in this module.)
+
+Here's what it looks like when you pass too few arguments to e.g. "mul".
+
+    >>> from joy.library import _dictionary
+    >>> m = _dictionary['*']
+    >>> m((), (), {})
+
+    Traceback (most recent call last):
+      File "<pyshell#49>", line 1, in <module>
+        m((), (), {})
+      File "joy/library.py", line 185, in mul:inner
+        (a, (b, stack)) = stack
+    ValueError: need more than 0 values to unpack
+    >>> 
+
+
+Notice that line 185 in the library.py file is (as of this writing) in
+the BinaryBuiltinWrapper's inner() function, but this hacky code has
+managed to insert the name of the wrapped function ("mul") along with a
+colon into the wrapper function's reported name.
+
+Normally I would frown on this sort of mad hackery, but...  this is in
+the service of ease-of-debugging!  Very valuable.  And note that all the
+hideous patching is finished in the module-load-stage, it shouldn't cause
+issues of its own at runtime.
+
+The main problem I see with this is that people coming to this code later
+might be mystified if they just see a traceback with a ':' in the
+function name!  Hopefully they will discover this documentation.
+'''
+
+
+def rename_code_object(new_name):
+  '''
+  If you want to wrap a function in another function and have the wrapped
+  function's name show up in the traceback, you must do this brutal
+  hackery to change the func.__code__.co_name attribute.  See:
+
+  https://stackoverflow.com/questions/29919804/function-decorated-using-functools-wraps-raises-typeerror-with-the-name-of-the-w
+
+  https://stackoverflow.com/questions/29488327/changing-the-name-of-a-generator/29488561#29488561
+
+  I'm just glad it's possible.
+  '''
+  def inner(func):
+    name = new_name + ':' + func.__name__
+    code_object = func.__code__
+    return type(func)(
+      type(code_object)(
+          code_object.co_argcount,
+          code_object.co_nlocals,
+          code_object.co_stacksize,
+          code_object.co_flags,
+          code_object.co_code,
+          code_object.co_consts,
+          code_object.co_names,
+          code_object.co_varnames,
+          code_object.co_filename,
+          name,
+          code_object.co_firstlineno,
+          code_object.co_lnotab,
+          code_object.co_freevars,
+          code_object.co_cellvars
+          ),
+      func.__globals__,
+      name,
+      func.__defaults__,
+      func.__closure__
+      )
+  return inner