Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ValueError: malformed node or string with ast.literal_eval() when adding a Keras layer

Tags:

I want to build a Keras Model evaluating strings. If I do the following:

from keras.models import Sequential
from keras.layers import Dense

model = Sequential()
model.add(Dense(units=10, input_shape=(10,), activation='softmax'))

It works fine. And I can see the model.summary().

But, when I add the layer with ast.literal_eval()

from keras.models import Sequential
from keras.layers import Dense
import ast

model = Sequential()
code = "model.add( Dense( input_shape=(10,), units=10, activation='softmax' ) )"
ast.literal_eval(code)

It throws me the next ValueError:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.5/ast.py", line 84, in literal_eval
    return _convert(node_or_string)
  File "/usr/lib/python3.5/ast.py", line 83, in _convert
    raise ValueError('malformed node or string: ' + repr(node))
ValueError: malformed node or string: <_ast.Call object at 0x7efc40c90e10>

If I use eval instead of ast.literal_eval it works too.

I'm using python3.5.

like image 795
Barrendeitor Avatar asked Aug 06 '18 16:08

Barrendeitor


People also ask

What does AST literal_eval do in Python?

ast. literal_eval() The ast module helps Python applications to process trees of the Python abstract syntax grammar. The literal_eval safely evaluate an expression node or a string containing a Python literal or container display.

What is from AST import 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 malformed string?

If you see this error, it usually means the the client is not able to transform the string you wrote to the character set acceptable to the Firebird server. Here's a short explanation how this works: In your client program, you type in some text, which is then shown on the screen.


1 Answers

A big mistake: literal_eval only works for literals. In this case, I have a Call.

The function literal_eval first parse the string.

From /usr/lib/python3.5/ast.py: lines 38-46

def literal_eval(node_or_string):
    """
    Safely evaluate an expression node or a string containing a Python
    expression.  The string or node provided may only consist of the following
    Python literal structures: strings, bytes, numbers, tuples, lists, dicts,
    sets, booleans, and None.
    """
    if isinstance(node_or_string, str):
        node_or_string = parse(node_or_string, mode='eval')

At this point, node_or_string is an instance of Expression. Then, literal_eval get the body.

From /usr/lib/python3.5/ast.py: lines 47-48

    if isinstance(node_or_string, Expression):
        node_or_string = node_or_string.body

And finally, literal_eval checks the type of the body (node_or_string).

From /usr/lib/python3.5/ast.py: lines 49-84

    def _convert(node):
        if isinstance(node, (Str, Bytes)):
            return node.s
        elif isinstance(node, Num):
            return node.n
        elif isinstance(node, Tuple):
            return tuple(map(_convert, node.elts))
        elif isinstance(node, List):
            return list(map(_convert, node.elts))
        elif isinstance(node, Set):
            return set(map(_convert, node.elts))
        elif isinstance(node, Dict):
            return dict((_convert(k), _convert(v)) for k, v
                        in zip(node.keys, node.values))
        elif isinstance(node, NameConstant):
            return node.value
        elif isinstance(node, UnaryOp) and \
             isinstance(node.op, (UAdd, USub)) and \
             isinstance(node.operand, (Num, UnaryOp, BinOp)):
            operand = _convert(node.operand)
            if isinstance(node.op, UAdd):
                return + operand
            else:
                return - operand
        elif isinstance(node, BinOp) and \
             isinstance(node.op, (Add, Sub)) and \
             isinstance(node.right, (Num, UnaryOp, BinOp)) and \
             isinstance(node.left, (Num, UnaryOp, BinOp)):
            left = _convert(node.left)
            right = _convert(node.right)
            if isinstance(node.op, Add):
                return left + right
            else:
                return left - right
        raise ValueError('malformed node or string: ' + repr(node))
    return _convert(node_or_string)

If the initial code was ast.literal_eval('1+1') (for example), now node_or_string would be an instance of BinOp. But in the case of:

code = "model.add( Dense( input_shape=(10,), units=10, activation='softmax' ) )"
ast.literal_eval(code)

The body will be an instance of Call, which does not appear among the valid types of the function.

E.g.:

import ast

code_nocall = "1+1"
node = ast.parse(code_nocall, mode='eval')
body = node.body
print(type(body)) # Returns <class '_ast.BinOp'>

code_call = "print('hello')"
node = ast.parse(code_call, mode='eval')
body = node.body
print(type(body)) # Returns <class '_ast.Call'>

Solution

The best solution I have found so far, to not use eval directly, is to perform the process manually. With this function:

import ast

def eval_code(code):
    parsed = ast.parse(code, mode='eval')
    fixed = ast.fix_missing_locations(parsed)
    compiled = compile(fixed, '<string>', 'eval')
    eval(compiled)

Now it works:

eval_code("print('hello world')")

from keras.models import Sequential
from keras.layers import Dense
model = Sequential()
code = "model.add( Dense( input_shape=(10,), units=10, activation='softmax' ) )"
eval_code(code)
like image 121
Barrendeitor Avatar answered Oct 04 '22 15:10

Barrendeitor