파이썬의 exec() 함수는 프로그램 내에서 코드를 동적으로 실행할 수 있게 해주는 강력한 내장 함수입니다. 실행할 코드는 문자열(string) 또는 코드 객체(code object) 형태로 전달할 수 있습니다. 문자열로 전달된 코드는 먼저 파싱(parsing)되고 문법 오류 여부를 검사한 뒤 실행되며, 코드 객체는 별도의 검사 없이 그대로 즉시 실행됩니다.
exec() 함수 기본 문법
exec(object, globals, locals)
각 매개변수의 의미는 다음과 같습니다.
object - 실행할 문자열 또는 코드 객체를 전달합니다.
globals - 사용 가능한 전역 변수와 전역 메서드를 담은 딕셔너리입니다.
locals - 사용 가능한 지역 변수와 지역 메서드를 담은 딕셔너리입니다.
문자열로 코드 실행하기
가장 간단한 사용 방법은 한 줄짜리 코드를 문자열로 전달하는 것입니다. 아래 예제에서는 문자열이 파싱된 후 실행되어 결과를 출력합니다.
x = 9
exec('print(5*x)')실행 결과
45
코드 객체 전달하기
여러 문장으로 구성된 코드 블록도 전달할 수 있습니다. 이 경우 코드 객체로 처리되므로 추가 파싱 없이 곧바로 실행됩니다. 참고로 \n(줄바꿈)과 공백을 활용해 올바른 들여쓰기(indentation)를 갖춘 파이썬 코드 블록을 만들 수 있습니다.
예제
prog_block = 'x = 3 \nif(x < 5): \n print x*x' exec(prog_block)
실행 결과
9
globals와 locals 매개변수 생략 시 동작
globals와 locals 매개변수에 값을 전달하지 않으면, 프로그램에 임포트된 패키지에서 기본적으로 제공하는 모든 함수와 변수에 접근할 수 있습니다. 아래 예제는 두 매개변수를 모두 생략했을 때 접근 가능한 전체 목록을 출력하는 코드입니다.
from time import *
exec("print(dir())")위 코드를 실행하면 다음과 같이 임포트된 모듈의 함수들(time, sleep, sqrt 등)과 내장 객체들이 모두 노출됩니다.
['In', 'Out', '_', '__', '___', '__builtin__', '__builtins__', '__doc__', '__file__', '__name__', '__package__', '_dh', '_exit_code', '_i', '_i1', '_i10', '_i11', '_i12', '_i13', '_i14', '_i15', '_i16', '_i17', '_i18', '_i19', '_i2', '_i20', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'clock', 'copysign', 'cos', 'cosh', 'ctime', 'daylight', 'degrees', 'e', 'erf', 'erfc', 'exit', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'get_ipython', 'gmtime', 'hypot', 'isinf', 'isnan', 'ldexp', 'lgamma', 'localtime', 'log', 'log10', 'log1p', 'mktime', 'modf', 'pi', 'pow', 'prog', 'prog_block', 'quit', 'radians', 'sin', 'sinh', 'sleep', 'sqrt', 'strftime', 'strptime', 'struct_time', 'tan', 'tanh', 'time', 'timezone', 'trunc', 'tzname', 'x']
globals 매개변수로 접근 제한하기
빈 딕셔너리({})를 globals 매개변수로 전달하면, 임포트된 모듈의 함수들에 대한 접근을 차단할 수 있습니다. 이 경우 결과에는 내장 함수만 표시되며, 외부에서 임포트한 모듈의 함수는 나타나지 않습니다. 이처럼 exec()의 접근 범위를 제한하여 보다 안전하게 사용할 수 있습니다.
예제
from time import *
exec("print(dir())",{})실행 결과
['__builtins__']
특정 함수만 허용하기
임포트된 모듈 중에서도 원하는 특정 함수만 선택적으로 허용할 수 있습니다. 아래 예제에서는 globals 옵션에 필요한 함수만 딕셔너리 형태로 전달합니다. localtime() 함수는 프로그램에 임포트된 time 모듈에 속하는 함수입니다.
예제
from time import *
exec("print lclt()",{"lclt":localtime})실행 결과
time.struct_time(tm_year=2019, tm_mon=7, tm_mday=19, tm_hour=12, tm_min=33, tm_sec=53, tm_wday=4, tm_yday=200, tm_isdst=0)
locals 매개변수로 세밀하게 제어하기
locals 매개변수를 활용하면 내장 함수까지 완전히 배제하고, 허용된 함수만 사용하도록 제한할 수 있습니다. 아래 예제에서는 내장 전역 매개변수인 __builtins__의 값으로 None을 지정하여 내장 함수 접근을 차단했습니다.
예제
from time import *
exec("print(dir())", {"__builtins__" : None}, {"gtime": gmtime, "print": print, "dir": dir})실행 결과
['dir', 'gtime', 'print']
결과를 보면 명시적으로 허용한 세 개의 함수만 접근 가능함을 확인할 수 있습니다. 이렇게 exec() 함수는 매우 유연하지만, 외부 입력을 그대로 실행하면 심각한 보안 취약점이 될 수 있으므로 반드시 globals와 locals 매개변수를 적절히 설정하여 실행 환경을 통제하는 것이 좋습니다.