Some attribute issue with Python 3.11 func.__code__ object

Hello,

I have a piece of project code that initiate the func.code object like this:

func.__code__ = type(func.__code__)(
          func.__code__.co_argcount,
          func.__code__.co_posonlyargcount,
          func.__code__.co_kwonlyargcount,
          func.__code__.co_nlocals,
          func.__code__.co_stacksize,
          func.__code__.co_flags,
          func.__code__.co_code,
          func.__code__.co_consts,
          func.__code__.co_names,
          func.__code__.co_varnames,
          filename or func.__code__.co_filename,
          func.__code__.co_name,
          line_number or func.__code__.co_firstlineno,
          func.__code__.co_lnotab,
          func.__code__.co_freevars,
          func.__code__.co_cellvars
      )

However, this code block works when python version <=3.10, and have this error on python 3.11:

TypeError: code() argument 13 must be str, not int

I tried to add str() around the 13th argument above, but not work. I am not sure what to do next, can anyone help? Thank you!

Let’s first use the interactive help to understand what is expected:

$ python3.11
Python 3.11.2 (main, Apr  5 2023, 03:08:14) [GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> help(type((lambda:0).__code__))
Help on class code in module builtins:

class code(object)
 |  code(argcount, posonlyargcount, kwonlyargcount, nlocals, stacksize, flags, codestring, constants, names, varnames, filename, name, qualname, firstlineno, linetable, exceptiontable, freevars=(), cellvars=(), /)
 |  
 |  Create a code object.  Not for the faint of heart.

You are missing a qualname, which now must be provided separately from the name, causing the reported error. You are also missing an exceptiontable. There isn’t very good documentation on this sort of thing, but naturally there are corresponding co_exceptiontable and co_qualname attributes on the previous code object.

However, your approach is “not the right way to do it”. If you want to make a modified version of an existing code object, use its .replace method instead:

>>> help((lambda:0).__code__.replace)
Help on built-in function replace:

replace(*, co_argcount=-1, co_posonlyargcount=-1, co_kwonlyargcount=-1, co_nlocals=-1, co_stacksize=-1, co_flags=-1, co_firstlineno=-1, co_code=None, co_consts=None, co_names=None, co_varnames=None, co_freevars=None, co_cellvars=None, co_filename=None, co_name=None, co_qualname=None, co_linetable=None, co_exceptiontable=None) method of builtins.code instance
    Return a copy of the code object with new values for the specified fields.

Therefore:

old = func.__code__
func.__code__ = old.replace(
    co_filename=filename or old.co_filename,
    co_firstlineno=line_number or old.co_firstlineno
)

This will automatically keep working if the code type gains more attributes/constructor parameters, since you only specify the parts that are being modified.

Better yet, instead of falling back on “replacing” attributes with themselves, try refactoring the setup code to build a dictionary of things to replace, and then just do something like old.replace(**replacements).

I was able to solicit some more information for you on Stack Overflow:

In particular see the recent answer by blhsing.