파이썬에서 코드 객체를 만드는 방법은?
함수 타입으로 새로운 코드 객체를 만들고 싶습니다.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))
이 함수에 전달된 두 개의 인수("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: []
칠레가 함수를 실행하는 것을 알 수 있듯이 스택에 3개 이상의 요소가 있는 경우는 없습니다(이 경우 tup이 길이로 계산됩니다)
flag의 값은 67 = 1000011 = 1000000 +10 +1 = 64 +2 +1 이다
- 코드는 최적화되어 있다(대부분의 자동 생성 코드가 그렇듯)
- 함수 바이트코드 로컬 네임스페이스 변경을 실행하는 동안
- 64? 사실 무슨 뜻인지 모르겠어
함수에 사용되는 유일한 전역 이름은 "c"이며 co_names에 저장됩니다
우리가 사용하는 모든 명시적 리터럴은 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.
'개발하자' 카테고리의 다른 글
플러터의 이미지 맵 같은 기능? (0) | 2023.09.27 |
---|---|
주피터 노트북을 사용할 때 "asyncio.run()은 실행 중인 이벤트 루프에서 호출할 수 없습니다." (0) | 2023.09.26 |
how to select a long list of id's in sql using python (0) | 2023.09.25 |
여러 줄 문자열 내에서 파이썬 대체 (0) | 2023.09.25 |
vscode 주피터에서 파이토치 진행률 막대가 사라짐 (0) | 2023.09.24 |