본문 바로가기

개발하자

파이썬에서 코드 객체를 만드는 방법은?

반응형

파이썬에서 코드 객체를 만드는 방법은?

함수 타입으로 새로운 코드 객체를 만들고 싶습니다.CodeType(). 이에 대한 문서는 거의 없고 기존 문서에는 "마음이 약해서는 안 된다"고 나와 있습니다. 제가 필요로 하는 것을 말해주고 유형별로 전달된 각 논쟁에 대한 정보를 주세요.CodeType(코드 유형), 예를 게시할 수 있습니다.

: 일반적인 사용 사례에서는 내장 함수 컴파일()이 필요합니다. 타입을 사용해야 합니다.CodeType()은 일반 소스 코드를 쓸 때 얻을 수 없고 바이트 코드에 직접 액세스해야 하는 새 명령을 생성하려는 경우에만 해당합니다.




––––––––––– : 본 답변의 문서는 공식적이지 않으며 부정확할 수 있습니다.

이 답변은 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
        )

이제 각 주장의 의미가 무엇인지 설명해보겠다.

함수에 전달할 인수 수입니다(*args 및 **kwargs는 포함되지 않습니다).

번호.

전역 이름을 제외한 모든 변수 및 매개 변수(*args 및 **kwargs 포함)의 로컬 변수 수입니다.

코드에 필요한 스택(가상 머신 스택)의 양, 어떻게 작동하는지 이해하고 싶다면 공식을 참조하십시오.

코드 개체에 대해 설명하는 비트맵: 1 –> 코드가 최적화됨 2 –> 새 로컬: 새 로컬 네임스페이스(예: 함수) 4 –> 코드가 임의 수의 위치 인수(*args 사용) 8 –> 코드가 임의 수의 키워드 지정 인수(*kwargs 사용) 32 –> 코드가 생성기임

다른 플래그들은 이전 파이썬 버전에서 사용되거나 ___에서 가져온 것을 말하기 위해 활성화된다

이해를 더 잘하려면 바이트 코드 명령을 나타내는 일련의 바이트(위와 동일)를 참조하십시오

바이트 코드에서 사용되는 리터럴(예: 사전 계산된 숫자, 튜플 및 문자열)을 포함하는 튜플

바이트 코드에서 사용하는 이름이 포함된 튜플로, 이 이름은 전역 변수, 함수 및 클래스 또는 개체에서 로드된 속성입니다

바이트 코드에서 사용하는 로컬 이름을 포함하는 튜플(인수가 먼저이고, 로컬 변수가 다음)

이것은 코드가 컴파일된 파일명이다. 네가 원하는 건 뭐든지 될 수 있어, 너는 이 일에 대해 자유롭게 거짓말 할 수 있어. ;)

그것은 그 기능의 이름을 알려준다. 또한 이것은 당신이 원하는 무엇이든 될 수 있지만, 조심하세요. 이것이 추적에 나타난 이름입니다. 만약 이름이 불분명하다면 추적이 불분명할 수 있습니다. 람다가 얼마나 성가실 수 있는지 생각해 보세요.

함수의 첫 줄(소스 코드를 컴파일한 경우 디버그 목적)

바이트 코드 오프셋과 줄 번호의 상관 관계를 나타내는 바이트 매핑입니다. (이것 또한 디버그 목적이라고 생각합니다. 이것에 대한 설명서는 거의 없습니다.)

자유 변수 이름이 들어 있는 튜플입니다. 자유 변수는 코드 개체가 정의된 네임스페이스에 선언된 변수이며, 중첩 함수가 선언될 때 사용됩니다. 이 경우 자유 변수도 전역 변수이기 때문에 모듈 수준에서는 발생하지 않습니다.

중첩 함수에서 참조하는 지역 변수 이름이 들어 있는 튜플입니다.

–––––––––––– : 위에서 말한 것의 의미를 다음의 예를 통해 명확히 해야 한다.

: 위에서 언급한 완성된 코드 객체 속성은 접두사를 가지며, 함수는 그 실행 바디를 속성에 저장한다

––––––––––––

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))
  1. 이 함수에 전달된 두 개의 인수("a", "b")가 있다

  2. 이 함수에는 두 개의 매개변수 ("a"b")와 세 개의 국소변수 ("k"w"p")가 있다

  3. 함수 바이트 코드를 분해하면 다음을 얻을 수 있습니다:

    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:  []
    

    칠레가 함수를 실행하는 것을 알 수 있듯이 스택에 3개 이상의 요소가 있는 경우는 없습니다(이 경우 tup이 길이로 계산됩니다)

  4. flag의 값은 67 = 1000011 = 1000000 +10 +1 = 64 +2 +1 이다

    • 코드는 최적화되어 있다(대부분의 자동 생성 코드가 그렇듯)
    • 함수 바이트코드 로컬 네임스페이스 변경을 실행하는 동안
    • 64? 사실 무슨 뜻인지 모르겠어
  5. 함수에 사용되는 유일한 전역 이름은 "c"이며 co_names에 저장됩니다

  6. 우리가 사용하는 모든 명시적 리터럴은 co_consts에 저장된다:

    • 없음은 함수의 반환 값입니다
    • 우리는 tw에게 번호 10을 명시적으로 부여한다
    • (1, '2,' 3)을 1위에 명시적으로 할당한다
    • 튜플이 상수라면, 튜플의 각 원소는 상수이므로, 1,2,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가 실행될 때 첫 번째 줄과 두 번째 줄이 인쇄되므로 G 코드의 co_freevars와 co_name을 보여준다: "FunctionVar"는 F 함수의 네임스페이스에 있고, G가 생성된 곳에서 대신 "ModuleVar"는 모듈 변수이므로 전역으로 간주된다.

다음 세 줄은 F 코드의 co_cellvars, co_freevars 및 co_names 속성에 관한 것이다. "FunctionVar"는 G 중첩 함수에서 참조되므로 cellvar로 표시되고, "ModuleVar"는 F가 생성된 네임스페이스에 있지만 모듈 변수이므로 freevar로 표시되지 않지만 전역 이름에서 찾을 수 있다. 또한 내장 함수 프린트는 이름과 F에 사용된 모든 속성의 이름으로 표시됩니다.

––––––––––––

이것은 작동 코드 개체 초기화입니다. 이것은 쓸모없지만 이 기능으로 원하는 모든 것을 할 수 있습니다.

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



CodeType 컨스트럭터의 사용 예는 표준 라이브러리, 특히 Lib/modulefinder.py 에서 찾을 수 있습니다. 거기를 보면 파일의 모든 코드 개체에서 읽기 전용 속성을 재정의하는 데 사용되는 것을 볼 수 있습니다.

최근 함수 공장이 있는 유사한 사용 사례를 접했는데, 생성된 함수는 항상 트레이스백에 '일반' 이름이 붙어 있어 원하는 이름을 담기 위해 코드 객체를 재생성해야 했다.

>>> 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!'

하지만 잠시만요, 이 기능의 멤버는 읽기 전용이 아니기 때문에 모듈파인더가 하는 일을 할 수 있습니다:

>>> 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

이 예에서 주의할 점은 스택 트레이스에서 값을 생성할 때 트레이스백이 속성이 아닌 속성을 사용한다는 것입니다.

참고 사항 하나 더: 위의 내용은 파이썬 3이므로 파이썬 2와 호환되도록 하려면 두 번째 인수는 생성자()에게 생략한다.

업데이트: 빅터 스티너는 파이썬 3.8의 CodeType 클래스에 'replace'라는 새로운 메서드를 추가하여 상황을 상당히 간소화했습니다. 이는 3.8에서도 'co_argcount' 뒤에 새로운 'co_posonlyargcount' 인수를 통화 목록에 추가했기 때문에 향후 호환성 문제를 제거하기 위해 수행된 것이므로 적어도 3.8 이상의 코드는 인수 목록이 다시 변경되면 어느 정도 미래에 증명될 것입니다.

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



@KarlKnechtel의 요청에 따라, 이 답변은 @Alberto Perella의 답변을 (이 글의 시점 기준으로, Python 3.11의 경우) 구축자, 바이트 코드 및 새로운 방법의 최신 정보 및 문서/소스로 보완하기 위한 것이다.

First of all, as the OP suggests, the official documentation for types.CodeType is extremely limited, with no real signature:

class types.CodeType(**kwargs)

So we have to piece together available information ourselves by looking at different but relevant sources.

For brief but reasonable explanations of all attributes of a CodeType object, we can find them in the code portion of the table in the Types and members section of the documentation for the inspect module.

However, that documentation has the attribute names listed in alphabetical order, rather than in the order the CodeType constructor expects them as positional arguments.

Since types.CodeType is defined in types.py as simply the type of the __code__ attribute of an ad-hoc function, with no signature definition written Python:

def _f(): pass
CodeType = type(_f.__code__)

the only definitive way to obtain the real signature of CodeType is to look at its reference implemention in CPython, where all arguments are nicely validated in the argument clinic for the code constructor by their positions and expected types.

For example, from this line:

argcount = PyLong_AsInt(PyTuple_GET_ITEM(args, 0));

we can see that the first argument (position 0) is argcount as an int object because PyLong_AsInt is called.

And from these lines:

if (!PyBytes_Check(PyTuple_GET_ITEM(args, 6))) {
    _PyArg_BadArgument("code", "argument 7", "bytes", PyTuple_GET_ITEM(args, 6));
    goto exit;
}
code = PyTuple_GET_ITEM(args, 6);

we can see that the 7th argument (position 6) is code as a bytes object because PyBytes_Check is called, and ditto for the rest of the arguments.

Unfortunately, if we go through the arguments actually validated by the argument clinic, we'll notice some discrepancies with what's listed in the aforementioned inspect documentation, namely that the inspect doc is missing co_linetable and co_exceptiontable, and that the argument clinic no longer has the documented co_lnotab. With a quick search, we can find that per PEP-626, co_linetable has replaced co_lnotab, which is now generated on-the-fly by the new co_lines method, and that per issue 47236, co_exceptiontable is now required to accompany co_code to hold exception handling information.

Although the format of co_linetable is claimed to be "opaque" according to PEP-626:

The co_linetable attribute will hold the line number information. The format is opaque, unspecified and may be changed without notice. The attribute is public only to support creation of new code objects.

it actually is well documented in locations.md.

With all of the above pieces of information from various sources, we can now put together a full list of arguments in the positional order expected by the CodeType constructor, along with their types and descriptions:

Position Argument Type Description
0 co_argcount int number of arguments (not including keyword only arguments, * or ** args)
1 co_posonlyargcount int number of positional only arguments
2 co_kwonlyargcount int number of keyword only arguments (not including ** arg)
3 co_nlocals int number of local variables
4 co_stacksize int virtual machine stack space required
5 co_flags int bitmap of CO_* flags, read more here
6 co_code bytes string of raw compiled bytecode
7 co_consts tuple tuple of constants used in the bytecode
8 co_names tuple tuple of names other than arguments and function locals
9 co_varnames tuple tuple of names of arguments and local variables
10 co_filename str name of file in which this code object was created
11 co_name str name with which this code object was defined
12 co_qualname str fully qualified name with which this code object was defined
13 co_firstlineno int number of first line in Python source code
14 co_linetable bytes line information encoded in a format specified here
15 co_exceptiontable bytes exception handling information encoded in a format specified here
16 co_freevars tuple tuple of names of free variables (referenced via a function’s closure)
17 co_cellvars tuple tuple of names of cell variables (referenced by containing scopes)

As for the documentation of bytecode, we can find it nicely laid out in the Python Bytecode Instructions section of the documentation of the dis module, with the option to switch to the documentation for an earlier Python version with the drop-down menu at the top of the page, while the actual mapping of each bytecode to its respective integer value can be found in _opcode_metadata.py.

Finally, although there has been an attempt to document the CodeType.replace method in bpo-37032, the end result in the official documentation of types is a very brief description with no examples:

replace(**kwargs)

Return a copy of the code object with new values for the specified fields.

Fortunately, we can find a good usage example in a unit test here:

def new_code(c):
    '''A new code object with a __class__ cell added to freevars'''
    return c.replace(co_freevars=c.co_freevars + ('__class__',), co_code=bytes([COPY_FREE_VARS, 1])+c.co_code)

As can be seen, the usage of CodeType.replace is fairly straightforward, that all of the arguments that the CodeType constructor takes can be passed to the replace method with a direct replacement value. The idea is to eliminate the need to rebuild a new CodeType object with a long list of arguments from an existing CodeType object when only a few of the attributes need changed, so that code written for an older Python version does not need to be refactored when parameters are added to or removed from the CodeType constructor in a new Python version.

Hope this helps.


반응형