파이썬의 eval() 함수는 인자로 전달된 표현식(expression)을 파싱한 뒤 프로그램 내에서 그대로 실행하는 내장 함수입니다. 쉽게 말해 문자열로 작성된 코드를 파이썬 프로그램 안에서 실제 코드처럼 해석해 실행해 주며, 이를 활용하면 실행 시점에 코드를 동적으로 구성할 수 있습니다.
문법(Syntax)
eval(expression, globals=None, locals=None)
expression – eval()에 전달되어 실행될 파이썬 표현식(문자열)입니다.
globals – eval() 내부에서 접근 가능한 전역 변수와 함수를 담는 딕셔너리입니다.
locals – eval() 내부에서 접근 가능한 지역 변수와 함수를 담는 딕셔너리입니다.
기본 예제
아래 예제는 사용자가 직접 표현식을 입력하면 프로그램이 이를 평가(evaluate)하여 결과를 출력하는 방식으로, eval()을 활용해 동적 코드를 구현하는 대표적인 사례입니다.
# 평가할 표현식 입력받기
user_expr = input("변수 a에 대한 표현식을 입력하세요: ")
# 변수 a의 값 입력받기
a = int(input("변수 a의 값을 입력하세요: "))
# 표현식 평가
result = eval(user_expr)
# 결과 출력
print("Result = {}".format(result))
실행 결과
변수 a에 대한 표현식을 입력하세요: a*(a-3)+a^2
변수 a의 값을 입력하세요: 7
Result = 33
참고: 위 예제는 파이썬 2의 raw_input()을 사용했지만, 파이썬 3부터는 input()으로 통합되었습니다. 또한 결과가 33인 이유는 파이썬에서 ^ 연산자가 거듭제곱이 아닌 비트 XOR 연산자이기 때문입니다. 즉, 7×(7−3) + (7 XOR 2) = 28 + 5 = 33이 됩니다. 거듭제곱을 원한다면 a**2를 사용해야 합니다.
eval()의 보안 취약점
웹 애플리케이션이나 데스크톱 프로그램처럼 외부 사용자의 입력을 받는 환경에서 eval()을 무분별하게 사용하면 심각한 보안 취약점이 발생할 수 있습니다. 악의적인 사용자가 파일 삭제나 시스템 명령 실행 같은 표현식을 입력해 시스템에 침투하거나 민감한 데이터를 빼낼 수 있기 때문입니다. 따라서 eval()을 반드시 사용해야 한다면, 접근 가능한 함수와 변수를 제한하는 것이 필수적입니다.
1. globals와 locals를 모두 생략하기
두 인자를 모두 생략하면 표현식은 현재 스코프(current scope) 안에서만 평가되므로, 스코프 밖의 변수에는 접근할 수 없습니다.
2. locals만 생략하고 globals에 빈 딕셔너리 전달하기
locals를 생략하면 모든 변수가 전역 변수로 취급됩니다. 여기에 globals로 빈 딕셔너리를 넘기면, 다른 라이브러리를 임포트했더라도 내장(built-in) 변수만 접근 가능하도록 제한할 수 있습니다.
from time import *
print(eval('dir()', {}))
실행 결과
['__builtins__']
반대로, 임포트한 라이브러리 중 특정 메서드만 허용하고 싶다면 딕셔너리에 해당 함수를 명시적으로 지정하면 됩니다.
from time import *
print(eval('dir()', {'sleeptime': sleep, 'Localtime': localtime}))
실행 결과
['Localtime', '__builtins__', 'sleeptime']
3. 전역·지역 스코프 모두에 선택적 함수만 전달하기
내장 함수는 아예 차단하고(__builtins__를 None으로 설정), 로컬에서 임포트한 라이브러리 중 필요한 일부 함수만 허용하는 방법도 있습니다. 아래 예제에서는 time 라이브러리의 gmtime 함수만 사용 가능하도록 제한했습니다.
from time import *
a = 1445945763
print(eval('gmtime(a)', {'__builtins__': None}, {'a': a, 'gmtime': gmtime}))
실행 결과
time.struct_time(tm_year=2015, tm_mon=10, tm_mday=27, tm_hour=11, tm_min=36, tm_sec=3, tm_wday=1, tm_yday=300, tm_isdst=0)
마무리
eval()은 문자열을 코드로 실행할 수 있는 강력한 기능이지만, 그만큼 위험도 큰 도구입니다. 외부 입력을 처리해야 하는 상황이라면 가급적 ast.literal_eval() 같은 더 안전한 대안을 먼저 고려하고, 부득이하게 eval()을 사용해야 할 때는 위에서 소개한 스코프 제한 기법을 반드시 적용하는 것이 좋습니다.