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.
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.
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.
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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With