Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the equivalent of Python's ast.literal_eval() in Julia?

Is there anything in Julia which is equivalent to Python's literal_eval provided by the package ast (Abstract Syntax Tree)?

A summary of its (literal_eval) description:

This function only evaluates Python literal structures: strings, bytes, numbers, tuples, lists, dicts, sets, booleans, and None, and can be used for safely evaluating strings from untrusted sources without the need to parse the values oneself. It is not capable of evaluating arbitrarily complex expressions, for example involving operators or indexing.

like image 583
aroyc Avatar asked May 26 '17 06:05

aroyc


People also ask

What is Python AST literal_eval?

The ast. literal_eval method is one of the helper functions that helps traverse an abstract syntax tree. This function evaluates an expression node or a string consisting of a Python literal or container display.

What is AST in Python?

Source code: Lib/ast.py. The ast module helps Python applications to process trees of the Python abstract syntax grammar. The abstract syntax itself might change with each Python release; this module helps to find out programmatically what the current grammar looks like.

How safe is AST literal_eval?

The documentation states it is safe, and there is no bug relative to security of literal_eval in the bug tracker, so you can probably assume it is safe. Also, according to the source, literal_eval parses the string to a python AST (source tree), and returns only if it is a literal.


2 Answers

There is no equivalent, although you could potentially write one fairly easily by parsing code and then recursively ensuring that you only have certain syntactic forms in the resulting expression before evaluating it. However, unlike Python where a lot of basic types and their syntax and behavior are built in and unchangeable, Julia's "built in" types are just user-defined types that happen to be defined before the system starts up. Let's explore what happens, for example, when you use vector literal syntax:

julia> :([1,2,3]) |> dump
Expr
  head: Symbol vect
  args: Array{Any}((3,))
    1: Int64 1
    2: Int64 2
    3: Int64 3
  typ: Any

julia> f() = [1,2,3]
f (generic function with 2 methods)

julia> @code_lowered f()
CodeInfo(:(begin
        nothing
        return (Base.vect)(1, 2, 3)
    end))

julia> methods(Base.vect)
# 3 methods for generic function "vect":
vect() in Base at array.jl:63
vect(X::T...) where T in Base at array.jl:64
vect(X...) in Base at array.jl:67

So [1,2,3] is just a syntactic form that is lowered as a call to the Base.vect function, i.e. Base.vect(1,2,3). Now, we might in the future make it possible to "seal" some functions so that one can't add any submethods or overwrite their behavior in any way, but currently modifying the behavior of Base.vect for some set of arguments is entirely possible:

julia> function Base.vect(a::Int, b::Int, c::Int)
           warn("SURPRISE!")
           return invoke(Base.vect, Tuple{Any,Any,Any}, a, b, c)
       end

julia> [1,2,3]
WARNING: SURPRISE!
3-element Array{Int64,1}:
 1
 2
 3

Since an array literal is overloadable in Julia it's not really a purely literal syntax. Of course, I don't recommend doing what I just did – "SURPRISE!" is not something you want to see in the middle of your program – but it is possible and therefore the syntax is not "safe" in the sense of this question. Some other constructs which are expressed with literals in Python or JavaScript (or most scripting languages), are explicitly function calls in Julia, such as constructing dictionaries:

julia> Dict(:foo => 1, :bar => 2, :baz => 42)
Dict{Symbol,Int64} with 3 entries:
  :baz => 42
  :bar => 2
  :foo => 1

This is just a function call to the Dict type with three pair object arguments, not a literal syntax at all. The a => b pair syntax itself is also just a special syntax for a function call to the => operator, which is an alias for the Pair type:

julia> dump(:(a => b))
Expr
  head: Symbol call
  args: Array{Any}((3,))
    1: Symbol =>
    2: Symbol a
    3: Symbol b
  typ: Any

julia> :foo => 1.23
:foo=>1.23

julia> =>
Pair

julia> Pair(:foo, 1.23)
:foo=>1.23

What about integer literals? Surely those are safe! Well, yes and no. Small integer literals are currently safe, since they are converted in the parser directly to Int values, without any overloadable entry points (that could change in the future, however, allowing user code to opt into different behaviors for integer literals). Large enough integer literals, however, are lowered to macro calls, for example:

julia> :(18446744073709551616)
:(@int128_str "18446744073709551616")

An integer literal that is too big for the Int64 type is lowered as macro call with a string argument containing the integer digits, allowing the macro to parse the string and return an appropriate integer object – in this case an Int128 value – to be spliced into the abstract syntax tree. But you can define new behaviors for these macros:

julia> macro int128_str(s)
           warn("BIG SUPRISE!")
           9999999999999999
       end    

julia> 18446744073709551616
WARNING: BIG SUPRISE!
9999999999999999

Essentially, there is no meaningful "safe literal subset" of Julia. Philosophically, Julia is very different from Python: instead of building in a fixed set of types with special capabilities that are inaccessible to user-defined types, Julia includes powerful enough mechanisms in the language that the language can be built from within itself – a process known as "bootstrapping". These powerful language mechanisms are just as available to Julia programmers as they are to the programmers of Julia. This is where much of Julia's flexibility and power comes from. But with great power comes great responsibility and all that... so don't actually do any of the things I've done in this answer unless you have a really good reason :)

To get back to your original problem, the best one could do to create a parser for safe literal object construction using Julia syntax would be to implement a parser for a subset of Julia, giving literals their usual meaning in a way that cannot be overloaded. This safe syntax subset could include numeric literals, string literals, array literals, and Dict constructors, for example. But it would probably be more practical to just use JSON syntax and parse it using Julia's JSON package.

like image 70
StefanKarpinski Avatar answered Oct 19 '22 09:10

StefanKarpinski


I know I'm late here, but Meta.parse does the job:

 julia> eval(Meta.parse("[1,2,3]"))
 3-element Array{Int64,1}:
  1
  2
  3

Specifically, Meta.parse turns your string into an Expr which eval then turns into a useable data structure. Definitely works in Julia 1.0. https://discourse.julialang.org/t/how-to-convert-a-string-into-an-expression/11160

like image 20
Alex Ellison Avatar answered Oct 19 '22 07:10

Alex Ellison