Как создать объект кода в python?

Я хотел бы создать новый объект кода с типами функций. CodeType().
Почти нет документации об этом, а существующая говорит "не для слабонервных"
Скажите мне, что мне нужно, и дайте мне некоторую информацию о каждом аргументе, переданном типам. CodeType,
возможно, разместив пример.

Примечание:
В нормальных случаях использования вам просто нужна встроенная функция compile()
Вы должны использовать types.CodeType() только в том случае, если вы хотите создать новые инструкции, которые не могут быть получены при записи обычного исходного кода и которые требуют прямого доступа к байт-коду.

Ответ 1

-----------
Отказ от ответственности:
Документация в этом ответе не является официальной и может быть неправильной.

Этот ответ действителен только для версии python 3.x

-----------

Чтобы создать объект кода, вы должны передать функции CodeType() следующие аргументы:

CodeType(
        argcount,             #   integer
        kwonlyargcount,       #   integer
        nlocals,              #   integer
        stacksize,            #   integer
        flags,                #   integer
        codestring,           #   bytes
        consts,               #   tuple
        names,                #   tuple
        varnames,             #   tuple
        filename,             #   string
        name,                 #   string
        firstlineno,          #   integer
        lnotab,               #   bytes
        freevars,             #   tuple
        cellvars              #   tuple
        )

Теперь я попытаюсь объяснить, в чем смысл каждого аргумента.

argcount
Количество аргументов, которые должны быть переданы функции (* args и ** kwargs, не включены).

kwonlyargcount
Число аргументы только для ключевого слова.

nlocals
Число локальных переменных,
а именно все переменные и параметры (* args и ** kwargs включены), кроме глобальных имен.

STACKSIZE Количество стека (стек виртуальной машины), требуемое кодом,
если вы хотите понять, как это работает, см. официальную Документация.

флаги
Растровое изображение, которое говорит что-то о объекте кода:
1 → оптимизирован код

2 → newlocals: существует новое локальное пространство имен (например, функция)
4 → код принимает произвольное число позиционных аргументов (используется * args)
8 → код принимает произвольное число аргументов с ключевыми словами (используется * kwargs)
32 → код является генератором

Флаги

othes используются в более старых версиях python или активируются, чтобы сказать, что импортировано из __ будущего __

codestring
Последовательность байтов, представляющая инструкции байткода
если вы хотите получить лучшее понимание, см. Документация (такая же, как указано выше)

consts
Кортеж, содержащий литералы, используемые байт-кодом (например, предварительно вычисленные числа, кортежи и строки)

имена
Кортеж, содержащий имена, используемые байт-кодом
эти имена являются глобальными переменными, функциями и классами, а также атрибутами, загружаемыми из объектов

имени переменной
Кортеж, содержащий локальные имена, используемые байтовым кодом (сначала аргументы, затем локальные переменные)

имя_файла
Это имя файла, из которого был скомпилирован код.
Это может быть то, что вы хотите, вы можете лгать об этом.;)

имя
Он дает имя функции. Также это может быть все, что вы хотите, но будьте осторожны:
это имя показано в трассировке, если имя нечеткое, трассировка может быть неясной,
просто подумайте о том, как лямбды могут раздражать.

firstlineno
Первая строка функции (для цели отладки, если вы скомпилировали исходный код)

lnotab
Отображение байтов, которое коррелирует смещение байт-кода с номерами строк.
(я думаю, что это также касается отладочной цели, документации об этом немного)

freevars
Кортеж, содержащий имена свободных переменных.
Свободные переменные - это переменные, объявленные в пространстве имен, где был определен объект кода, они используются при объявлении вложенных функций.
это не происходит на уровне модуля, потому что в этом случае свободные переменные также являются глобальными переменными.

cellvars
Кортеж, содержащий имена локальных переменных, на которые ссылаются вложенные функции.

------------
Примеры:
следующие примеры должны разъяснять смысл сказанного выше.

Примечание: в атрибутах завершенных объектов кода, упомянутых выше, есть префикс co_,
и функция сохраняет свой исполняемый объект в атрибуте ___ код __

------------
1-й пример

def F(a,b):
    global c
    k=a*c
    w=10
    p=(1,"two",3)

print(F.__code__.co_argcount)
print(F.__code__.co_nlocals , F.__code__.co_varnames)
print(F.__code__.co_stacksize)
print(F.__code__.co_flags)
print(F.__code__.co_names)
print(F.__code__.co_consts)

Вывод:

2
5 ('a', 'b', 'k', 'w', 'p')
3
67
('c' ,)
(None, 10, 1, 'two'. 3, (1, 'two', 3))
  • есть два аргумента, переданных этой функции ( "a", "b" )

  • эта функция имеет два параметра ( "a", "b" ) и три локальные переменные ( "k", "w", "p" )

  • разбор байт-кода функции мы получаем:

    3         0 LOAD_FAST                0 (a)             #stack:  ["a"] 
              3 LOAD_GLOBAL              0 (c)             #stack:  ["a","c"]
              6 BINARY_MULTIPLY                            #stack:  [result of a*c]
              7 STORE_FAST               2 (k)             #stack:  []
    
    4        10 LOAD_CONST               1 (10)            #stack:  [10]
             13 STORE_FAST               3 (w)             #stack:  []
    
    5        16 LOAD_CONST               5 ((1, 'two', 3)) #stack:  [(1,"two",3)]
             19 STORE_FAST               4 (p)             #stack:  []
             22 LOAD_CONST               0 (None)          #stack:  [None]
             25 RETURN_VALUE                               #stack:  []
    

    поскольку вы можете заметить, что chile выполняет функцию, у нас никогда не бывает более трех элементов в стеке (в этом случае кортеж рассчитывается как его длина)

  • Значение флага - dec 67 = bin 1000011 = bin 1000000 +10 +1 = dec 64 +2 +1, поэтому мы понимаем, что

    • код оптимизирован (поскольку большая часть автоматически сгенерированного кода)
    • при выполнении функции поменять местами локальное пространство имен байт-кода
    • 64? На самом деле я не знаю, что это значит.
  • единственным глобальным именем, которое используется в функции, является "c", оно сохраняется в co_names

  • каждый явный литерал, который мы используем, хранится в co_consts:

    • Нет - это возвращаемое значение функции
    • мы явно присваиваем число 10 w
    • мы явно присваиваем (1, 'two', 3) p
    • если кортеж является константой, каждый элемент этого набора является константой, поэтому 1, "два", 3 являются константами

------------
Второй пример

ModuleVar="hi"

def F():
    FunctionVar=106
    UnusedVar=ModuleVar

    def G():
        return (FunctionVar,ModuleVar)

    print(G.__code__.co_freevars)
    print(G.__code__.co_names)

F()
print(F.__code__.co_cellvars)
print(F.__code__.co_freevars)
print(F.__code__.co_names)

Вывод:

('FunctionVar',)
('ModuleVar',)
('FunctionVar',)
()
('print', '__code__', 'co_freevars', 'co_names', 'ModuleVar')

Значение вывода таково:

первая и вторая строки печатаются, когда F выполняется, поэтому они показывают co_freevars и co_names из G-кода:
"FunctionVar" находится в пространстве имен функции F, где G было создано,
"ModuleVar" вместо этого является переменной модуля, поэтому он считается глобальным.

следующие три строки относятся к символам co_cellvars, co_freevars и co_names F-кода:
"ФункцияVar" ссылается в G-вложенной функции, поэтому она помечена как cellvar,
"ModuleVar" находится в пространстве имен, где F был создан, но это модульная переменная,
поэтому он не помечен как freevar, но он встречается в глобальных именах.
также встроенная функция print отмечена в именах, и все имена атрибутов, используемые в F.

------------
3-й пример

Это инициализация рабочего кода кода,
это не полезно, но вы можете делать все, что хотите, с помощью этой функции.

MyCode= CodeType(
        0,
        0,
        0,
        3,
        64,
        bytes([101, 0, 0,    #Load print function
               101, 1, 0,    #Load name 'a'
               101, 2, 0,    #Load name 'b'
               23,           #Take first two stack elements and store their sum
               131, 1, 0,    #Call first element in the stack with one positional argument
               1,            #Pop top of stack
               101, 0, 0,    #Load print function
               101, 1, 0,    #Load name 'a'
               101, 2, 0,    #Load name 'b'
               20,           #Take first two stack elements and store their product
               131, 1, 0,    #Call first element in the stack with one positional argument
               1,            #Pop top of stack
               100, 0, 0,    #Load constant None
               83]),         #Return top of stack
        (None,),
        ('print', 'a', 'b'),
        (),
        'PersonalCodeObject',
        'MyCode',
        1,
        bytes([14,1]),
        (),
        () )

a=2
b=3
exec(MyCode) # code prints the sum and the product of "a" and "b"

Вывод:

5
6

Ответ 2

Пример использования конструктора CodeType можно найти в стандартной библиотеке, в частности в Lib/modulefinder.py. Если вы посмотрите туда, то увидите, что он используется для переопределения атрибута co_filename только для чтения для всех объектов кода в файле.

Недавно я столкнулся с аналогичным случаем использования, когда у меня была фабрика функций, но сгенерированные функции всегда имели "универсальное" имя в трассировке, поэтому мне пришлось заново генерировать объекты кода, чтобы они содержали желаемое имя.

>>> def x(): raise NotImplementedError
...
>>> x.__name__
'x'
>>> x.__name__ = 'y'
>>> x.__name__
'y'
>>> x()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in x
NotImplementedError

>>> x.__code__.co_name
'x'
>>> x.__code__.__name__ = 'y'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: readonly attribute

>>> 'Gah!'
'Gah!'

Но, подождите, член функции __code__ не только для чтения, поэтому мы можем делать то, что делает modulefinder:

>>> from types import CodeType
>>> co = x.__code__
>>> x.__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, co.co_filename,
             'MyNewCodeName',
             co.co_firstlineno, co.co_lnotab, co.co_freevars,
             co.co_cellvars)
>>> x.__code__.co_name
'MyNewCodeName'
>>> x()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in MyNewCodeName
NotImplementedError

В этом примере следует отметить, что при получении значений в трассировке стека трассировка использует атрибут co_name, а не атрибут func.__name__.

Еще одно примечание: выше приведен Python 3, чтобы сделать его совместимым с Python 2, просто оставьте второй аргумент конструктору (co_kwonlyargcount).

ОБНОВЛЕНИЕ: Виктор Стиннер добавил новый метод 'replace' в класс CodeType в Python 3.8, который значительно упрощает ситуацию. Это было сделано, чтобы устранить будущие проблемы совместимости, так как 3.8 также добавил новый аргумент "co_posonlyargcount" в список вызовов после "co_argcount", так что, по крайней мере, ваш 3.8 и более поздние коды будут в будущем защищены, если список аргументов снова изменится.

>>> x.__code__ = x.__code__.replace(co_name='MyNewCodeName')