1. 5
  1.  

  2. 1

    I’m wondering if you could, instead of using eval to make functions with the right names, allocate some function objects and assign strings to their name (or is it func_name?) attribute? Or is that one read-only? I remember some of the attributes of function objects are read-only but not all of them are.

    1. 2

      In Python 2.7, you can do this by constructing a new code object and assigning it to a function’s func_code attribute. The function’s func_name is ignored by the traceback generation. Still, no eval necessary!

      Running this:

      from types import CodeType
      
      def make_raiser(name, lineno, filename, type_to_raise):
          def raises():
              raise type_to_raise()
          co = raises.func_code
          raises.func_name = name
          raises.func_code = CodeType(
              co.co_argcount,
              co.co_nlocals,
              co.co_stacksize,
              co.co_flags,
              co.co_code,
              co.co_consts,
              co.co_names,
              co.co_varnames,
              filename,
              name,
              lineno - 1,
              co.co_lnotab,
              co.co_freevars,
              co.co_cellvars,
          )
          return raises
      
      foo = make_raiser('foo', 3, 'foo.rs', lambda: IndexError(0))
      bar = make_raiser('bar', 4, 'bar.rs', foo)
      bar()
      

      with python2.7 gives me this output:

      $ python2 maketraceback.py 
      Traceback (most recent call last):
        File "maketraceback.py", line 27, in <module>
          bar()
        File "bar.rs", line 4, in bar
        File "foo.rs", line 3, in foo
      IndexError: 0
      

      For Python3, there’s one extra argument to the CodeType constructor, so it’s just:

      from types import CodeType
      
      def make_raiser(name, lineno, filename, type_to_raise):
          def raises():
              raise type_to_raise()
          co = raises.__code__
          raises.__name__ = name
          raises.__code__ = CodeType(
              co.co_argcount,
              co.co_kwonlyargcount,
              co.co_nlocals,
              co.co_stacksize,
              co.co_flags,
              co.co_code,
              co.co_consts,
              co.co_names,
              co.co_varnames,
              filename,
              name,
              lineno - 1,
              co.co_lnotab,
              co.co_freevars,
              co.co_cellvars,
          )
          return raises
      
      foo = make_raiser('foo', 3, 'foo.rs', lambda: IndexError(0))
      bar = make_raiser('bar', 4, 'bar.rs', foo)
      bar()
      

      and output looks the same:

      $ python3 maketraceback3.py 
      Traceback (most recent call last):
        File "maketraceback3.py", line 29, in <module>
          bar()
        File "bar.rs", line 4, in bar
        File "foo.rs", line 3, in foo
      IndexError: 0
      
      1. 1

        Yes, this is what I’m planning for the next post :)