Firstly, do NOT directly use types.CodeType and use pyobject.Code instead, as constructing types.CodeType is too complex and not compatible across multiple Python versions.
The pyobject library, which can be installed via pip install pyobject, provides a high-level wrapper for code objects.
After testing it on Python 3.11, the output is:
E:\Git-repositories\Github-publish\pyc-zipper>py311
Python 3.11.8 (tags/v3.11.8:db85d51, Feb 6 2024, 22:03:32) [MSC v.1937 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import random
>>> from pyobject import Code
>>> def f(a):return a+1
...
>>> c=Code(f.__code__)
>>> new_co_code = bytes([random.randint(0, 255) for _ in range(24)])
>>> new_c=c.copy()
>>> new_c.co_code=new_co_code # pyobject.Code is mutable
>>> new_code=new_c.to_code()
>>> new_code.co_code == new_co_code
False
>>> c.co_code
b'\x97\x00|\x00d\x01z\x00\x00\x00S\x00'
>>> new_code.co_code
b'\x90ik^\x00\x00\x00\x00\xa6\xec\x00\x00x\x8adR=\xeb\x00\xe7\x00\x8cT\x90'
As I tried to disassembly it:
>>> c.dis()
1 0 EXTENDED_ARG 105
Traceback (most recent call last):
...
IndexError: tuple index out of range
>>> new_co_code=bytearray(new_co_code) # convert to bytearray
>>> new_co_code[1]=new_co_code[3]=0 # modify bytes at 1 and 3 to 0 to observe them clearly
>>> new_co_code=bytes(new_co_code) # convert it back to bytes
>>> new_co_code
b'"\x00k\x00d0Ef\x8f\xec\xf9\xc8x\x8adR=\xeb\xe7\xe7\xc8\x8cT\x90'
>>> c.co_code=new_co_code
>>> c.to_code().co_code
b'\x90\x00k\x00\x00\x00\x00\x00\xa6\xec\x00\x00x\x8adR=\xeb\x00\xe7\x00\x8cT\x90'
>>> c.dis()
1 0 EXTENDED_ARG 0
2 COMPARE_OP 0 (<)
...
So I guess that Python 3.11 automatically adds EXTENDED_ARG opcode that extends the original one-byte argument when some irregular bytecodes are detected.
>>> new_co_code=bytearray(new_co_code)
>>> new_co_code[0]=ord('d')
>>> new_co_code=bytes(new_co_code)
>>> c.co_code=new_co_code
>>> c.to_code().co_code
b'd\x00k\x00\x00\x00\x00\x00\xa6\xec\x00\x00x\x8adR=\xeb\x00\xe7\x00\x8cT\x90'
>>> c.dis()
1 0 LOAD_CONST 0 (None)
2 COMPARE_OP 0 (<)
8 PRECALL 236
12 COPY 138
Traceback (most recent call last):
...
IndexError: tuple index out of range
The first opcode is set to LOAD_CONST, and the EXTENDED_ARG is not added automatically.
Furthermore:
>>> new_co_code=bytearray(new_co_code)
>>> new_co_code[0]=0x90 # directly set to EXTENDED_ARG
>>> new_co_code=bytes(new_co_code)
>>> c.co_code=new_co_code
>>> c.to_code().co_code
b'\x90\x00k\x00\x00\x00\x00\x00\xa6\xec\x00\x00x\x8adR=\xeb\x00\xe7\x00\x8cT\x90'
>>> c.dis()
1 0 EXTENDED_ARG 0
2 COMPARE_OP 0 (<)
8 PRECALL 236
...
It indicates that some other opcodes, including COMPARE_OP will be modified automaticlly.