Safely eval Python Syntax

using the AST module

Use Cases

  • Expression matching events
  • Provide a query syntax
    • Create your own "DSL"

The Dangers of eval & exec

  • Standard advice is to avoid eval
  • evalhas valid uses

Why is eval dangerous?

  • Can provide our own locals and globals dicts
    eval(supplied_syntax, {}, {})
  • But Python is Meta!

Lets Do Something Nasty...

syntax = """
    s for s in ().__class__.__base__.__subclasses__() 
    if s.__name__ == 'Quitter'
][0]('', 0)()
eval(syntax, {}, {})

More info on eval

See Ned Batchelders excellent talk on the topic.

Use the AST

  • In the Standard Library
  • Creates a syntax tree
  • Used by your favourite tools (pytest, black, bandit)

What does a Syntax tree look like

>>> ast.parse("obj.x == 123", mode="eval")

            value=Name(id='obj', ctx=Load()), 

ppast thanks to


  • Literals eg "foo", 123, True, None
  • Variables
  • Expressions eg Operators, comparison, call , subscripting, comprehensions
  • Statements assignment, print, raise, del, import...
  • Control flow if/else, try/except, for, while, with...
  • Function and Class Def, Async

What to Allow?

Depends on your application!

  • Only allow a single expression, use eval mode
    ast.parse(expression, mode="eval")
  • Filter any ast.Call instances eg in ("any", "all")
  • Not allow dunders
    not attr.attr.startswith("__")

Code example!

Back to eval

  • Just validate syntax

What else can you do?

  • Edit the AST
  • Generate our own code

Useful things we can do

  • Define our own DSL?
  • Tweak behaviour
  • Apply fine-grained permissions

Enhancing an expression

# The expression ==

# With an extra check
( is not None) and ( ==


has_permission(left, "name") and ( ==


  • Email:
  • Twitter: @savage_drummer
  • Github:
  • PyPI: odin or pyapp