2.34. 동적 코드¶
세 가지 내장 함수가 Python 소스 문자열을 받아 실행합니다: eval(), exec(), compile(). 이들을 함께 사용하면 코드가 런타임에 더 많은 코드를 구성하고 실행할 수 있습니다 – 이는 가끔은 정확히 알맞은 도구이지만, 그보다 훨씬 더 자주 버그와 보안 구멍의 원천이 됩니다.
경고
이 함수들은 임의의 Python을 실행합니다. 사용자 입력 – 사용자가 편집할 수 있는 파일의 문자열, 네트워크로 받은 페이로드, REPL 프롬프트에 입력된 값 – 을 eval 이나 exec 에 전달하면, 그 입력이 호출하는 스크립트가 할 수 있는 모든 일을 할 수 있게 됩니다. 장치의 모든 파일을 삭제하는 것까지 포함해서 말입니다. 신중하게 사용하고, 자동으로 실행되는 코드 안에서는 절대 사용하지 말며, 통제할 수 없는 데이터에는 절대 사용하지 마세요.
2.34.1. eval¶
eval() 은 단일 식(expression) 을 실행하고 그 값을 반환합니다:
>>> eval("3 * 7")
21
>>> name = "OpenMV"
>>> eval("name.lower()")
'openmv'
식은 기본적으로 호출자의 전역과 지역을 참조하므로, 두 번째 예제에서 name 이 해석됩니다. 명시적인 딕셔너리를 전달하면 평가를 샌드박스화할 수 있습니다:
eval("a + b", {"__builtins__": None}, {"a": 1, "b": 2})
샌드박스 처리를 하더라도 eval 은 위험합니다. 그러한 샌드박스를 벗어나는 잘 알려진 기법들이 있으므로, 신뢰할 수 없는 입력에 대해서는 빈 __builtins__ 트릭만으로 의존하지 마세요.
2.34.2. exec¶
exec() 는 단일 식이 아니라 코드 블록(block) – 문, 함수 정의, 루프 – 을 실행하고 None 을 반환합니다:
exec("for i in range(3): print(i)")
출력:
0
1
2
블록은 이후에 사용할 수 있는 이름을 정의할 수 있지만, 지역 대 전역 범위와 관련해 몇 가지 주의 사항이 있습니다. 함수 내부에서의 exec 는 작성자가 기대하는 대로 동작하는 경우가 드뭅니다; 꼭 필요하다면 모듈 수준에서 실행하세요.
2.34.3. compile¶
compile() 은 소스 문자열을 나중에 eval() 이나 exec() 에 전달할 수 있는 코드 객체(code object) 로 바꿉니다. 동일한 소스가 여러 번 실행될 때 사용하세요 – 파싱은 한 번만 일어나고, 실행은 더 빠릅니다:
expr = compile("x * x", "<expr>", "eval")
for x in range(5):
print(eval(expr))
출력:
0
1
4
9
16
가운데 인자는 코드가 예외를 일으킬 때 트레이스백에 나타나는 레이블입니다. 세 번째 인자는 단일 식의 경우 "eval", 블록의 경우 "exec", 결과를 출력하는 대화형 스타일의 문의 경우 "single" 입니다.
2.34.4. 언제 이것들에 손을 대야 하는가¶
거의 절대 사용하지 마세요. 초보자가 떠올리는 대부분의 사용 사례에는 더 안전한 대안이 있습니다:
설정 파일 읽기.
json을 사용하세요 – 구조화된 데이터이며, 실행이 없습니다.사용자가 입력한 숫자 값 평가하기.
int()/float()로 파싱한 다음 산술 연산을 하세요. 사용자가 정말로 수식을 입력해야 한다면,eval이 아니라 작은 식 파서를 사용하세요.
정말로 eval / exec / compile 이 필요하다면, 호출 지점을 격리하고, 실행되려는 정확한 문자열을 로그로 남기며, 그 소스를 코드에서 가장 의심스러운 대상으로 취급하세요.