exec
문자열이 exec
직접 (적절한 들여 쓰기로) 대체 된 것처럼 함수 내에서 문자열 이 가능한지 궁금합니다 . 99.9 %의 경우에는 사용하지 말아야한다는 것을 이해하지만,해야할지 여부보다이 exec
작업을 수행 할 수 있는지에 더 관심이 있습니다.
내가 원하는 동작은 다음과 같습니다.
GLOBAL_CONSTANT = 1
def test_func():
def A():
return GLOBAL_CONSTANT
def B():
return A()
return B
func = test_func()
assert func() == 1
그러나 나는 대신 주어진다.
GLOBAL_CONSTANT = 1
EXEC_STR = """
def A():
return GLOBAL_CONSTANT
def B():
return A()
"""
def exec_and_extract(exec_str, var_name):
# Insert code here
func = exec_and_extract(EXEC_STR, 'B')
assert func() == 1
def exec_and_extract(exec_str, var_name):
exec(EXEC_STR) # equivalent to exec(EXEC_STR, globals(), locals())
return locals()[var_name]
NameError: name 'A' is not defined
호출 할 때 func()
부터 A
그리고 B
내 존재 exec_and_extract
의 locals()
실행하는 동안 만 실행 컨텍스트 A
또는 B
이다 exec_and_extract
의 globals()
.
def exec_and_extract(exec_str, var_name):
exec(EXEC_STR, locals()) # equivalent to exec(EXEC_STR, locals(), locals())
return locals()[var_name]
NameError: name 'GLOBAL_CONSTANT' is not defined
호출 할 때 A
내부에서 func()
의 실행 컨텍스트 이후 A
인 exec_and_extract
의 locals()
이 포함되어 있지 않습니다 GLOBAL_CONSTANT
.
def exec_and_extract(exec_str, var_name):
exec(EXEC_STR, globals()) # equivalent to exec(EXEC_STR, globals(), globals())
return globals()[var_name]
작동하지만 동등하지 않은 전역 네임 스페이스를 오염시킵니다.
def exec_and_extract(exec_str, var_name):
locals().update(globals())
exec(EXEC_STR, locals()) # equivalent to exec(EXEC_STR, locals(), locals())
return locals()[var_name]
작품 만의 전체 내용을 복사 할 필요가 exec_and_extract
의 globals()
로의 locals()
경우 시간 낭비 globals()
대형 (이 인위적인 예에서는 적용되지 물론)입니다. 또한 인수 중 하나 exec_and_extract
가 GLOBAL_CONSTANT
(끔찍한 인수 이름) 인 경우 동작이 다르기 때문에 "코드에 붙여 넣기"버전과 미묘하게 동일하지 않습니다 ( "붙여 넣기"버전은 인수 값을 사용하는 반면 코드는 전역 상수 값을 사용합니다).
문제 설명의 "허점"을 덮으려고합니다.
exec_str
값들은 글로벌 또는 로컬 변수 영역을 액세스 할 수있는 임의의 코드를 표현한다.exec_str
.exec_and_extract
(글로벌 네임 스페이스 또는 기타)에 대한 후속 호출 사이에 "오염"이 없어야합니다 . 즉,이 예에서의 실행은 향후를 호출 할 때 참조 할 수 있도록 EXEC_STR
남겨두면 안됩니다 .A
exec_and_extract
이것은 불가능 해. exec
지역 변수 범위 역학과 나쁘게 상호 작용하며 이와 같은 것이 작동하기에는 너무 제한적입니다. 사실, 실행 된 문자열의 모든 로컬 변수 바인딩 작업exec
은 기본 로컬로 호출 하는 경우 일반 할당, 함수 정의, 클래스 정의, 가져 오기 등을 포함하여 정의되지 않은 동작 입니다. 문서 인용 :
기본 로컬은 아래의 locals () 함수에 대해 설명 된대로 작동합니다. 기본 로컬 사전에 대한 수정을 시도해서는 안됩니다. 함수 exec ()가 반환 된 후 지역에 대한 코드의 영향을 확인해야하는 경우 명시 적 지역 사전을 전달합니다.
또한, 코드가 실행될 exec
수없는 return
, break
, yield
호출자 대신 다른 제어 흐름을 실행하거나. 그것은 할 수있는 break
실행 된 코드의 일부, 또는이다 루프 return
실행 된 코드에 정의 된 함수에서, 그러나 그것은 상호 작용 호출자의 제어 흐름과 함께 할 수 없습니다.
호출 함수의 로컬 (주석에서 언급했듯이)과 상호 작용할 수있는 요구 사항을 희생하고 호출자의 제어 흐름과 상호 작용하는 데 신경 쓰지 않는다면 코드의 AST를 새 함수 정의의 본문을 작성하고 다음을 실행합니다.
import ast
import sys
def exec_and_extract(code_string, var):
original_ast = ast.parse(code_string)
new_ast = ast.parse('def f(): return ' + var)
fdef = new_ast.body[0]
fdef.body = original_ast.body + fdef.body
code_obj = compile(new_ast, '<string>', 'exec')
gvars = sys._getframe(1).f_globals
lvars = {}
exec(code_obj, gvars, lvars)
return lvars['f']()
실수로 입력에서 삼중 따옴표로 묶인 문자열에 추가 들여 쓰기를 삽입하는 것과 같은 문제를 방지하기 위해 문자열 형식 대신 AST 기반 접근 방식을 사용했습니다.
inspect
호출자가 다른 모듈에 있더라도 자신의 전역이 exec_and_extract
아닌을 호출 한 사람의 전역을 사용할 수 있습니다 exec_and_extract
.
실행 된 코드에 정의 된 함수는 복사본이 아닌 실제 전역을 참조합니다.
수정 된 AST의 추가 래퍼 기능은 그렇지 않으면 발생할 수있는 일부 범위 문제를 방지합니다. 특히, 그렇지 않으면 예제 코드에서의 정의 B
를 볼 수 없습니다 A
.
이 기사는 인터넷에서 수집됩니다. 재 인쇄 할 때 출처를 알려주십시오.
침해가 발생한 경우 연락 주시기 바랍니다[email protected] 삭제
몇 마디 만하겠습니다