LESSON 99 · SAFE PYTHON30 min

Parse Python Literals Without eval

Use ast.literal_eval for literal syntax while enforcing an application-specific shape and size limit.

Before you start: Lists · Exceptions · Functions

Example check: Worked and guided code samples are checked against CPython 3.13. Practice uses Pyodide 0.29.5 (Python 3.13) in your browser. How we check lessons

Video lesson: Parse Python Literals Without eval

The preview is stored on this site. YouTube loads only when you press Play. Watch on YouTube

Read the video transcript

Select a timestamp to watch that moment on YouTube.

  1. 00:00

    Sometimes a program receives text that looks like a Python list, tuple, number, or dictionary. The tempting shortcut is `eval(text)`, because it converts many expressions into values. But `eval` evaluates Python expressions. If the text comes from an untrusted source, that gives the text far more power than a data parser should have. `ast.literal_eval` is a narrower standard- library function that accepts Python literal structures while rejecting general expressions. Today we will show the distinction without running any harmful input. We will then build a small parser that also checks the expected result type and input length.

  2. 00:42

    Even `literal_eval` is not a limitless safety boundary: very large or deeply nested input can still consume resources. If you control the data format, JSON is often the clearer interchange format. This lesson is about a specific situation where the input is intentionally Python- literal syntax. Watch the actual runs and ask what the parser is allowed to create, not merely whether it returns something for one string. The first string looks like a list literal, and `eval` returns a list. The second string is an arithmetic expression, and `eval` computes it. We intentionally use a harmless expression so the demonstration is safe, but the

  3. 01:25

    capability is the warning: this is execution, not limited data parsing. If a program accepts arbitrary text from a form, a file, or a message and passes it to `eval`, it has crossed a trust boundary. Filtering a few suspicious words is not a reliable repair, because Python expressions can be written in many forms. We need a parser whose permitted language is narrower by design. It is also worth checking the output type. Even if a string is a valid literal, receiving a dictionary when the application expects a list can cause a different class of bug. We will use `literal_eval` for the syntax boundary and an explicit type check for

  4. 02:06

    the application contract. `literal_eval` accepts the list and dictionary literal syntax. It rejects the arithmetic expression because that is not a literal structure. This is a much better match for a program that expects data rather than behavior. The allowed forms include basic literal values and containers; it does not resolve arbitrary variable names or call functions. That restriction is the feature. However, accepting any literal does not mean the result is suitable for our use. The dictionary in this run is valid literal syntax, but a feature expecting a list of integers should reject it.

  5. 02:48

    Also, the parser can still consume memory or stack resources if given extremely large or deeply nested input. The documentation explicitly warns against treating it as entirely safe for untrusted, unbounded data. Limit input size and complexity at the application boundary. For a regular configuration or network format, prefer a format with a clear schema and parser, such as JSON, when it matches the product's needs. The helper limits input length before parsing, calls `literal_eval`, and turns parser failures into a clear application error. It then checks for exactly the data shape we need: a list whose members have type

  6. 03:29

    `int`. We use `type(item) is int` here because Python's `bool` is a subclass of `int`; a plain `isinstance(True, int)` would accept a Boolean score. Whether that is desirable depends on the application, but for this example we want actual integer scores. The 200-character limit is illustrative and does not by itself guarantee a strict memory bound for every form of input. A production service should set limits based on its threat model and test them. We also do not make arbitrary expressions safe by catching exceptions around `eval`; the important fix is the parser choice.

  7. 04:09

    If you have a JSON array from an API, use `json.loads` instead of parsing Python syntax. Choose one format deliberately, validate the resulting data, and reject unexpected types early. The valid list passes. The arithmetic expression is rejected by the literal parser. The dictionary is a valid literal but the wrong shape for scores. The list with `True` is also a valid literal and a list, yet it violates the exact integer-item rule. That sequence shows three separate checks: allowed syntax, expected container, and expected element type. A program that stops after only the first check still needs to handle wrong-shape

  8. 04:51

    data somewhere else. Try adding a very long input string and observe the explicit length error before parsing. Then try a tuple such as `(1, 2)` and explain why our parser rejects it even though it is literal syntax. The application's contract says list. The takeaway is to avoid converting untrusted text into executable Python code, select an appropriate data parser, and validate the result's shape and limits. Complete the practice task by accepting one intended data form and showing that both an expression and a wrong type fail cleanly.

Understand the concept

eval executes a Python expression. If input comes from a user or an untrusted file, eval grants that text far more power than a data parser needs. A string that looks like a list might contain a function call or other behavior. Do not try to make eval safe by removing a few dangerous words; a blacklist is not a parser or a security boundary.

ast.literal_eval accepts only Python literal structures such as strings, numbers, tuples, lists, dictionaries, sets, booleans, and None. It rejects a function call such as sum([1, 2]) instead of executing it. That is safer for literal data, but not an unlimited-input service: very large or deeply nested inputs can consume substantial resources. For general data interchange, JSON is often clearer and more portable.

The checked parse_int_list function deliberately accepts only a small list of non-Boolean integers. It checks the input text length before parsing, translates syntax or malformed literal errors to ValueError, and then checks the resulting type, item count, and each integer's range. bool is a subclass of int in Python, so isinstance(True, int) alone would mistakenly accept True. Use type(value) is int under this exact contract.

Tests cover a valid list, an expression that literal_eval must reject, a tuple instead of a list, a Boolean, an oversized value, and an overlong input. This two-layer approach separates syntax parsing from business validation. It does not claim to make arbitrary attacker-controlled input risk free; a production network service should also apply request and resource limits.

  • eval executes expressions
  • literal_eval parses literal syntax only
  • Parsed shape needs separate validation
  • Bound input size before parsing

See it step by step

Read the code, predict the output, then compare it with the result.

01. Parse a literal list

PYTHON
import ast
print(ast.literal_eval('[2, -3, 5]'))
print(type(ast.literal_eval('[2, -3, 5]')).__name__)
EXPECTED OUTPUT
[2, -3, 5]
list

Literal syntax produces ordinary Python values without evaluating function calls.

02. Reject code-like input

PYTHON
import ast
try:
    ast.literal_eval('sum([1, 2])')
except (ValueError, SyntaxError):
    print('expression rejected')
EXPECTED OUTPUT
expression rejected

A call is not a literal and is not executed.

03. Spot the Boolean-as-integer trap

PYTHON
print(isinstance(True, int))
print(type(True) is int)
EXPECTED OUTPUT
True
False

Exact type checks exclude booleans when the contract says integers only.

A closer look

Follow the reasoning, inspect each result, then try the suggested changes in the console below.

01 / 03

Parse only literal structures

The first run passes a textual list into ast.literal_eval and receives an ordinary Python list. The output also prints its type. That is enough for a small literal-data exercise without granting the input access to function calls, imports, or other expression behavior that eval would execute.

Parsing is only the first boundary. A list could still contain the wrong item types, too many entries, or numbers outside the application's allowed range. The checked parser must separately enforce its own list-of-integers contract. If the data is exchanged with other languages, JSON is often a more portable format than Python literal syntax.

PYTHON
import ast
print(ast.literal_eval('[2, -3, 5]'))
print(type(ast.literal_eval('[2, -3, 5]')).__name__)
EXPECTED OUTPUT
[2, -3, 5]
list
Follow the reasoning
  1. Parse the literal list without eval.
  2. Inspect the resulting Python type.
  3. Validate the expected application shape afterward.
02 / 03

Reject code-like input without running it

The next example offers a function call as text. literal_eval rejects it and the handler prints expression rejected. No sum call executes. This is the crucial distinction from eval, which evaluates Python expressions and should not be used as a general parser for external input.

A limited parser is not a license to accept unlimited text. Deeply nested or huge literals can consume significant resources even without executing arbitrary code. Check input length before parsing, then catch SyntaxError and ValueError and report a controlled validation failure. A network-facing service needs request and resource limits as well.

PYTHON
import ast
try:
    ast.literal_eval('sum([1, 2])')
except (ValueError, SyntaxError):
    print('expression rejected')
EXPECTED OUTPUT
expression rejected
Follow the reasoning
  1. Offer a function call to literal_eval.
  2. Catch its controlled rejection.
  3. Enforce size and shape limits around the parser.
03 / 03

Exclude booleans from an integer-only contract

The last run prints True for isinstance(True, int), then False for type(True) is int. Python's Boolean type subclasses int, so a broad isinstance check would accidentally admit True into a list promised to contain only integer values. Under this exercise's exact rule, use the concrete type check.

After parsing, verify the outer value is a list, cap its length, inspect every item, and check the allowed numeric range. Keep these domain checks separate from syntax checks so error messages can point to the actual problem. Tests should cover a tuple, a Boolean, an expression, and an overlong string, not only one successful list.

PYTHON
print(isinstance(True, int))
print(type(True) is int)
EXPECTED OUTPUT
True
False
Follow the reasoning
  1. Compare isinstance and exact type for True.
  2. Apply the exact integer rule to every parsed item.
  3. Test wrong shapes, sizes, and ranges independently.

Try it in Python

Edit the example and run it. Python starts in your browser the first time you click Run.

Python console

Ready to run
Need input()? Add one value per line
Ctrl / ⌘ + Enter to run
OUTPUT
Your output appears here.
Your turnComplete parse_int_list(text). Require a string of at most 200 characters; parse with ast.literal_eval, require a list of at most 10 values, and require each value to have exact type int in -1000..1000. Raise ValueError for any invalid input. Prepared output must be [2, -3, 5].
Need a hint?

Reject wrong type and long text first. Catch (ValueError, SyntaxError, TypeError, MemoryError, RecursionError) from literal_eval; then validate type(result) is list, length, and type(item) is int.

Complete the task and select Check task to verify your code.

    Quick quiz

    Three questions. You can change your answers and try again.

    01.Why avoid eval for untrusted literal-looking data?

    02.Does literal_eval enforce that the result is a short list of integers?

    03.Why use type(value) is int in this contract?

    Typical mistakes

    Everyone meets these errors. See what causes them and how to fix them.

    Using eval as a parser

    COMMON MISTAKE
    values = eval(user_text)
    FIX
    values = ast.literal_eval(user_text)

    What happens: The input can execute code instead of supplying data.
    Parse literal syntax with a limited parser, then validate it.

    Accepting every parsed literal

    COMMON MISTAKE
    return ast.literal_eval(text)
    FIX
    if type(values) is not list or any(type(x) is not int for x in values):
        raise ValueError('integer list required')

    What happens: Tuples, booleans, huge values, or nested data slip through.
    Apply the application's exact shape and range contract.

    Browse this lesson’s problem fixes

    Sources and further reading