개발을 하다 보면 소스코드 내에서 문자열로 구성된 코드를 컴파일하여 실행해야 하는 경우가 종종 발생한다. 겪어보지 않았다면 이해가 어려울 수 있지만, 일을 하다 보면 생각보다 자주 있기 때문에 어떤 개념인지와 활용 예제를 통해서 하나씩 살펴보면 좋을 것 같다.
compile() 함수의 개념
compile() 함수는 파이썬 코드를 컴파일하여 코드 객체를 반환하는 내장 함수다. 일반 문자열로 표현된 파이썬 코드를 실행 가능하도록 컴파일 시켜준다.
컴파일
compile() 함수는 파이썬 코드를 컴파일하여 바이트 코드로 변환하며, 파이썬 인터프리터가 코드를 실행하기 전에 수행되는 단계다.
코드 객체
compile() 함수는 컴파일된 코드를 나타내는 코드 객체를 반환한다. 이 코드 객체는 eval() 함수나 exec() 함수를 통해서 실행이 가능하다.
유연성
compile() 함수를 사용하면 다양한 형식의 코드를 컴파일할 수 있다. 문자열, 바이트, AST(Abstract Syntax Tree) 노드 등을 인자로 전달할 수 있다.
동적 코드 실행
compile() 함수를 사용하면 동적으로 생성된 코드를 실행할 수 있다. 즉, 프로그램 실행 중 문자열로 표현된 코드를 컴파일하여 실행할 수 있다.
컴파일러 플래그
compile() 함수는 코드를 컴파일할 때 추가적인 컴파일러 플래그를 지정할 수 있는데, 이를 통해 컴파일러 과정을 제어하고 최적화 수준을 조정할 수 있다.
compile() 함수 사용 방법
compile() 함수는 3개의 매개변수를 받아 컴파일 처리를 한다. 각각의 사용 방법을 알아보자.
기본 구문
compile(source, filename, mode)
compile() 함수의 기본 구문은 위와 같이 작성할 수 있으며, source, filename, mode 3개의 매개변수를 받아 처리할 수 있다.
source
컴파일할 파이썬 코드를 포함하는 문자열, 코드 객체, AST(Abstract Syntax Tree)
filename
컴파일된 코드를 연관시킬 파일의 이름을 지정할 수 있다. 설정을 하지 않으면 기본값으로 ‘<string>'으로 처리된다.
mode
컴파일 모드를 지정하는 매개변수로서, ‘exec', 'eval', 'single' 세 종류가 있다.
위의 매개변수를 통해서 다양한 상황에 컴파일 및 실행 처리가 가능하다. 다음 활용 사례를 통해서 자세히 알아보자.
compile() 함수의 활용
compile() 함수는 다양한 상황에서 활용될 수 있다. compile() 함수를 사용하여 코드 객체를 생성하고 실행하는 방법까지 하나씩 살펴보자.
문자열 코드
source_code = """
def greet(name):
return f'Hello, {name}!'
"""
# 문자열을 코드 객체로 컴파일
code = compile(source_code, '<string>', 'exec')
# exec로 실행하여 함수 정의
exec(code)
# 함수 호출
print(greet('Sancode'))
# 출력: Hello, Sancode!
위의 코드는 문자열로 작성된 일반 함수 코드다. compile() 함수를 통해서 해당 함수 문자열 코드를 컴파일하고, 그 후 exec() 함수를 통해서 코드를 실행할 수 있다.
compile() 함수의 매개변수
첫 번째 매개변수는 컴파일하고자 하는 대상 코드 정보다.
두 번째 매개변수는 소스 코드의 이름이나 코드의 출처를 나타내는 문자열이며, 위의 코드에서는 문자열로부터 코드를 가져왔기 때문에 <string>으로 지정하였다.
세 번째 매개변수는 컴파일 모드이며, exec는 여러 문장을 포함할 수 있는 코드를 의미한다.
동적 연산 코드
expression = "3 * (4 + 5) - 7 / 2"
# 표현식을 코드 객체로 컴파일
code = compile(expression, '<string>', 'eval')
# eval로 계산하여 결과 반환
result = eval(code)
print(result)
# 출력: 23.5
위의 코드는 수학 연산 표현식을 컴파일하고 동적으로 실행하는 코드다. 일반 문자열로 수학 연산식을 작성하였고, 해당 코드 역시 compile() 함수를 통해 컴파일할 수 있다.
세 번째 매개변수에서 컴파일 모드를 eval이라고 지정하였는데, eval은 exec와 다르게 코드를 실행하고 실행한 후 연산의 결괏값을 반환할 수 있는 기능이다. 연산식이기 때문에 연산 후의 결과값을 얻고자 할 때 eval을 사용하는 것이 좋다.
사용자 입력 코드
user_code = input("Enter a Python expression: ")
try:
# 사용자 입력을 코드 객체로 컴파일
code = compile(user_code, '<string>', 'eval')
# eval로 실행하여 결과 반환
result = eval(code)
print(f"Result: {result}")
except Exception as e:
print(f"Error: {e}")
위의 코드는 사용자가 입력한 값을 기준으로 컴파일하고 실행하여 결괏값까지 추출하는 예제 코드다. 사용자가 입력한 값이 결과를 반환해야 하는 코드일 수 있어 실행 모드는 eval로 설정하였다.
파일 코드
# 파일에 코드 쓰기
with open('sample_code.py', 'w') as f:
f.write("""
def add(a, b):
return a + b
result = add(5, 7)
print(result)
""")
# 파일에서 코드 읽기
with open('sample_code.py', 'r') as f:
file_code = f.read()
# 파일 코드를 코드 객체로 컴파일
code = compile(file_code, 'sample_code.py', 'exec')
# exec로 코드 실행
exec(code)
# 출력: 12
위의 코드는 sample_code.py라는 파이썬 코드의 파일을 읽어 파일 내부의 코드를 컴파일하고 실행하는 예제 코드다.
open() 함수를 통해 파일을 생성하고, 두 번째 인자 ‘w'를 지정하여 작성 모드로 파이썬 코드를 작성하였다.
그 후, 작성된 코드 파일을 'r' 설정을 통해 읽기 모드로 파일을 읽어 파일 내의 코드를 추출하여 컴파일을 하였다.
마지막으로 exec() 함수를 통해 해당 코드를 실행할 수 있다.
이와 같이 파일을 통해서도 compile() 함수를 사용하여 컴파일이 가능하다.
이렇게 다양한 상황에서 compile() 함수를 사용하여 활용할 수 있다. 여기서 compile() 함수는 실행 목적이 아닌 컴파일이 목적이기 때문에 컴파일 후의 코드를 exec() 함수나 eval() 함수를 사용하여 실행해줘야 한다.
exec()
exec() 함수는 문자열로 된 파이썬 코드를 동적으로 실행할 수 있게 해주는 함수다.
임의의 파이썬 코드 블록을 실행할 수 있으며, 실행 결과를 반환하지 않는다.
eval()
eval() 함수는 문자열로 된 파이썬 코드를 동적으로 실행하여 결과를 반환하는 함수다.
문자열로 표현된 파이썬 코드를 실행하여 그 결과를 얻을 수 있지만, 안전하지 않은 코드에 대해서 사용하면 취약점이 될 수 있다.
compile() 함수 사용 시 주의사항
compile() 함수를 사용하여 문자열로 된 코드를 실행할 수 있다는 것은 활용도가 높아 다양한 상황에서 사용할 수 있다. 그러나, 프로세스 처리 중에 사용자를 통해서 입력받은 값을 검증 없이 컴파일하여 실행하게 되면 보안상의 큰 이슈가 발생할 수 있다. 그 외에도 여러 가지 주의사항을 살펴보자.
보안 문제
위에서 언급했듯이 compile() 함수를 사용할 때 사용자가 입력한 코드값을 아무런 검증 절차 없이 컴파일 후 실행한다는 것은 아주 위험하다. 가급적이면 입력받은 코드를 컴파일하는 것을 지양하거나 최대한 엄격한 검증을 통해 적용해야 한다.
실행 가능 코드
실행 가능한 코드를 생성할 때, 해당 코드의 영향을 신중하게 검토하고 제한해야 한다. 외부 데이터에 의해 생성된 코드를 실행할 때에는 특히 조심해야 한다.
성능
대용량 및 복잡한 코드를 컴파일하는 경우에는 성능에 영향을 줄 수 있다. 컴파일 대상 코드의 용량이나 복잡도를 검토한 후에 처리하는 것이 좋다.
디버깅
컴파일된 코드의 디버깅은 일반적으로 원본 파이썬 코드의 디버깅보다 어려울 수 있다. 컴파일된 코드를 변경하거나 수정하는 경우에는 이러한 문제를 고려해야 한다.
컴파일 코드 이식
컴파일된 코드는 실행하는 환경에 따라 이식성의 문제가 발생할 수 있다. 따라서 가능한 경우 컴파일된 코드를 최소화하고 이식성을 고려해야 한다.
위와 같이 다양한 주의사항을 확인할 수 있는데, 가장 중요한 부분은 역시 보안 문제가 될 것 같다. 특정 서용자가 악의적으로 코드를 입력하게 되는 경우 시스템에 아주 큰 타격을 줄 수 있으므로, 코드상에서 컴파일을 처리하는 것은 내부적으로만 사용하거나 엄격한 제한을 주는 것이 좋을 것 같다.
마무리
소스 코드상에서 업무 처리 중 별도의 컴파일을 진행하는 것은 강력한 기능일 수 있다. 그러나 위의 정리한 바와 같이 보안을 반드시 고려해야 해야 한다는 것을 알 수 있었다. 그래도 이러한 기능을 알아두는 것으로도 충분히 의미가 있기 때문에 문제가 될 수 있어도 기능들을 많이 접해보고 숙지하는 것이 좋다. 어디서 언젠가 어떻게 쓰일지 모르니까.
경제 지표 서비스
Economy Flow
econoflow.co.kr
'Python' 카테고리의 다른 글
파이썬 유니코드의 코드 포인트를 확인하는 ord() 함수의 개념과 활용 (0) | 2024.05.25 |
---|---|
파이썬 문자열을 평가하는 eval() 함수의 개념과 활용! (0) | 2024.05.24 |
파이썬 객체를 탐색하는 dir() 함수의 개념과 활용! (0) | 2024.05.21 |
파이썬 정렬 함수 sorted()에 대한 개념과 활용! (0) | 2024.05.20 |
파이썬 필터링을 위한 filter() 함수의 개념과 활용! (0) | 2024.05.18 |