Skip to content

compile() Function Complexity

The compile() function compiles Python source code into a code object.

Complexity Analysis

Operation Time Space Notes
Parse code O(n) O(n) n = source code length
Generate bytecode O(n) O(n) Proportional to AST size
Total O(n) O(n) Parsing + bytecode generation

Note: compile() is O(n) in the length of the source code. The resulting code object can be reused with eval()/exec() to avoid re-parsing.

Basic Usage

Compiling Expressions

# O(n) - compile to code object
code = compile("2 + 3", "<string>", "eval")
result = eval(code)  # 5

# Reuse compiled code (faster than re-parsing)
for i in range(1000):
    eval(code)  # No re-parsing

Compiling Statements

# O(n) - parse multiple statements
code = compile("""
x = 10
y = 20
z = x + y
""", "<string>", "exec")

namespace = {}
exec(code, namespace)
print(namespace['z'])  # 30

Single Interactive Statement

# O(n) - single statement mode
code = compile("x = 5", "<string>", "single")
exec(code)
# x is now 5 in current scope

Complexity Details

Parsing Overhead

# O(n) - where n = code length
short_code = "x = 1"
compile(short_code, "<string>", "exec")  # O(5)

long_code = "x = " + "1 + 2 + 3 + ... + 100"
compile(long_code, "<string>", "exec")  # O(n)

# Deeply nested code - still O(n)
nested_code = "((((1))))"
compile(nested_code, "<string>", "exec")  # O(n)

Modes

# All O(n), but different requirements

# "eval" - single expression only
code = compile("x + 1", "<string>", "eval")

# "exec" - multiple statements
code = compile("x = 1\ny = 2", "<string>", "exec")

# "single" - single statement or expression (interactive)
code = compile("x = 1", "<string>", "single")

Performance Patterns

Reusing Compiled Code

# Without compilation - O(n) per execution
for i in range(10000):
    eval("x ** 2")  # Parses each time - O(10000 * n)

# With compilation - O(n) once
code = compile("x ** 2", "<string>", "eval")
for i in range(10000):
    eval(code)  # O(n + 10000)

# Second approach is 10000x faster for repeated execution

Pre-compilation Benefits

import timeit

expr = "sum(range(100))"

# No pre-compilation
time1 = timeit.timeit(lambda: eval(expr), number=10000)

# Pre-compiled
code = compile(expr, "<string>", "eval")
time2 = timeit.timeit(lambda: eval(code), number=10000)

# time2 is significantly faster (no parsing)

Code Objects

# O(n) - produces code object
code_obj = compile("x + y", "<string>", "eval")

# Properties of code object
print(code_obj.co_names)       # Variable names: ('x', 'y')
print(code_obj.co_consts)      # Constants: (None,)
print(code_obj.co_code)        # Bytecode

# Reusable multiple times
result1 = eval(code_obj, {"x": 1, "y": 2})  # 3
result2 = eval(code_obj, {"x": 5, "y": 10})  # 15

Compilation with Optimization

# O(n) - optimize flag (Python 2 feature, deprecated in 3)
# Python 3 ignores optimize parameter

# In Python 3, compile is always well-optimized
code = compile("x = 1", "<string>", "exec", optimize=-1)

Error Handling

# O(n) - syntax checking happens during compilation
try:
    code = compile("x = ", "<string>", "exec")  # SyntaxError
except SyntaxError as e:
    print(f"Syntax error: {e}")

# vs runtime error (would happen during exec)
try:
    code = compile("undefined_function()", "<string>", "exec")
    # Compiles fine - error at runtime
except:
    pass

Practical Examples

Dynamic Function Generation

# O(n) - compile and execute function definition
func_code = """
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)
"""

code = compile(func_code, "<string>", "exec")
namespace = {}
exec(code, namespace)

fib = namespace['fibonacci']
print(fib(10))  # 55

Template Engine

# O(n) - compile template
template = "Hello {{name}}, you have {{count}} messages"

# Simple substitution approach
code = compile(f'f"{template}"', "<string>", "eval")

data = {"name": "Alice", "count": 5}
# result = eval(code, data)  # Doesn't work directly with f-strings
# Use proper template engines like Jinja2 instead

Configuration Script Execution

# O(n) - load and execute configuration
config_script = """
DEBUG = True
DATABASE = "sqlite:///app.db"
SECRET_KEY = "my-secret"
ALLOWED_HOSTS = ["localhost", "127.0.0.1"]
"""

code = compile(config_script, "config.py", "exec")
config = {}
exec(code, config)

debug = config['DEBUG']
database = config['DATABASE']

Bytecode Inspection

import dis

# O(n) - compile and analyze
code = compile("x = 1 + 2", "<string>", "exec")

# View bytecode
dis.dis(code)
# Output shows:
#   1           0 LOAD_CONST               1 (3)
#               2 STORE_NAME               0 (x)
#               4 LOAD_CONST               0 (None)
#               6 RETURN_VALUE

# Helps understand Python's execution

Filename Parameter

# O(n) - filename helps with error messages
code = compile("invalid python", "script.py", "exec")
# SyntaxError will show: File "script.py", line 1

# Use meaningful filenames for debugging

Comparison with eval()/exec()

# eval(string) - compile + execute in one
eval("x + 1")  # O(n) - includes parsing

# exec(string) - compile + execute in one
exec("x = 1")  # O(n) - includes parsing

# Separate compilation - better for reuse
code = compile("x + 1", "<string>", "eval")
eval(code)  # O(m) - no parsing, just execution

Security Considerations

# compile() doesn't execute, just parses
# Still requires careful input handling

user_input = "__import__('os').system('rm -rf /')"
try:
    code = compile(user_input, "<string>", "eval")
    # Code object created successfully
    # Actual execution happens at eval(code)
except SyntaxError:
    pass

# Use ast.parse() for safer parsing without execution
import ast
try:
    tree = ast.parse(user_input)
    # Can inspect without executing
except SyntaxError:
    pass

Edge Cases

Empty Code

# O(1) - minimal parsing
code = compile("", "<string>", "exec")
exec(code)  # Does nothing

Comment-only Code

# O(1) - ignored
code = compile("# This is a comment", "<string>", "exec")
exec(code)  # No operation

Mixed Whitespace

# O(n) - parses normally
code = compile("""

x = 1


y = 2

""", "<string>", "exec")
exec(code)

Performance Considerations

vs Source Execution

import timeit

# Direct execution
t1 = timeit.timeit("eval('2 + 3')", number=10000)

# Compiled execution
t2 = timeit.timeit("eval(code)", setup="code = compile('2 + 3', '<string>', 'eval')", number=10000)

# t2 is much faster (no parsing)

Memory Usage

# Code objects are memory efficient
code = compile("x + y" * 1000, "<string>", "eval")
# Bytecode is more compact than original string

Best Practices

Do:

  • Use compile() for code executed multiple times
  • Save compiled code in variables for reuse
  • Use meaningful filenames for debugging
  • Pre-compile hot paths in performance-critical code

Avoid:

  • Compiling code that runs only once (overhead > benefit)
  • Using compile() as security mechanism (it's not)
  • Ignoring SyntaxErrors from user input
  • Storing compiled code of untrusted source without review

Version Notes

  • Python 2.x: compile() accepts optional optimize flag
  • Python 3.x: Optimize parameter deprecated (always optimized)
  • Python 3.8+: Improved error messages
  • All versions: Returns code object, requires eval()/exec() to execute