You can hack ast.literal_eval to make it return a frozenset when it sees a set. Here is how to do:
- search where the library for your Python installation is
- it contains the file
ast.py that contains the function literal_eval - copy that function (using a different name) in you own module and change it to import all the relevant names from the
ast module - in the line processing a
Set, replace the generation of a set with a frozenset
You can then use it to safely parse literal sets containing sets. For my Python 3.5, I used:
def frozen_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. SPECIAL: This version uses frozensets instead of sets """ # SPECIAL: import names from ast module from ast import parse, Expression, Str, Bytes, Num, Tuple, List, Set, Dict from ast import NameConstant, UnaryOp, UAdd, USub, BinOp, Add, Sub # END SPECIAL if isinstance(node_or_string, str): node_or_string = parse(node_or_string, mode='eval') if isinstance(node_or_string, Expression): node_or_string = node_or_string.body 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): #SPECIAL: returns a frozenset return frozenset(map(_convert, node.elts)) # END SPECIAL 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)
I could the use:
>>> s = '{ 1, 2, {3, 4}}' >>> frozen_literal_eval(s) frozenset({1, 2, frozenset({3, 4})})
{1, 2, {3, 4}}stored as a string? (If yes, then you can't){1, 2, {3, 4}}is not a malformed string and is an acceptable input, but sets are still non-hashable,frozensetsmight be the way to approach the problem.