-
-
Notifications
You must be signed in to change notification settings - Fork 294
Provide actual literal_eval() function. Adapted from standard library's. #1347
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Use that function in inference.py instead of the dummy _to_literal() function.
for more information, see https://pre-commit.ci
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @tristanlatr. However, I don't really see why it is necessary to maintain our own helper function? If I replace helpers.literal_eval
in the tests for ast.literal_eval
it works as well?
Wouldn't it also work if we accepted str
for _to_literal
as well and just passed the string instead of node.as_string()
to ast.literal_eval
?
@@ -114,6 +114,8 @@ def __init__( | |||
class AstroidSyntaxError(AstroidBuildingError): | |||
"""Exception class used when a module can't be parsed.""" | |||
|
|||
# this should probably be a SyntaxError subclass. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We also re-raise it from TypeError
, ValueError
and LookupError
. So it doesn't seem to only be a SyntaxError
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know, this is out of the scope if this PR anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't the comment then not also be out of scope?
self.assertRaises(ValueError, helpers.literal_eval, "2+3") | ||
|
||
def test_literal_eval_complex(self): | ||
# Issue #4907 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which issue is this referring to?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Python's
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps add a reference to it then. Referring to issues from other projects/repo without any context can be confusing I think.
astroid/helpers.py
Outdated
if isinstance(node_or_string, str): | ||
_node = builder.parse(node_or_string.lstrip(" \t")).body | ||
if len(_node) != 1: | ||
raise ValueError(f"expected only one expression, found {len(_node)}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this covered? I don't see a test for it, or am I overlooking something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add a test
astroid/helpers.py
Outdated
if isinstance(node_or_string, str): | ||
_node = builder.parse(node_or_string.lstrip(" \t")).body | ||
if len(_node) != 1: | ||
raise ValueError(f"expected only one expression, found {len(_node)}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
raise ValueError(f"expected only one expression, found {len(_node)}") | |
raise ValueError(f"Expected only one expression, got {len(_node)}") |
astroid/helpers.py
Outdated
|
||
def _raise_malformed_node(node): | ||
msg = "malformed node or string" | ||
lno = getattr(node, "lineno", None) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you be willing to check if this is indeed necessary for astroid
nodes? I would assume we initialise lineno
on every node, even on Module
after #1262.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For nodes created from scratch it might be necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That shouldn't be the case. The init
of each node should handle setting lineno
. If they do not so this would be a bug and should be fixed.
astroid/helpers.py
Outdated
and node.func.name == "set" | ||
and node.args == node.keywords == [] | ||
): | ||
return set() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't tested. Seeing as this is apparently problematic in ast
(see python/cpython@4fcf5c1) I wonder how we handle dict()
? Perhaps we should add a test as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
set()
is a special case because we can't create empty set other wise.
I don;t think we should add this kind of support for dict()
or list()
since we can use {}
and []
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yeah. Still, if we're going to maintain our own function shouldn't we add some obvious extra functionality while we're at it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get your point, we could handle empty calls to list
, dict
, tuple
, if it's only that, I'm ok implementing it.
But in general, I think we should stay the closest possible from ast
module, and let users call this function with a possible inferred node.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I think it makes sense to add those three as well. There is a big chance that we will run out of sync with the "upstream" version of this due to limited maintenance time. Makes sense imo to add some slight improvements while we're looking at it.
No, astroid nodes and ast nodes are not 100% compatible.
Yes but the cool thing with
I'ts necessary if we don't want to convert back to string all |
The tests you added do work with
Hmm, that's indeed a benefit. However, I wonder if this warrants having to maintain this ourselves. We only use |
I agree it could work by doing something like: def _dummy_literal_eval(node: Union[str, NodeNG]) -> Any:
return ast.literal_eval(node if isinstance(node, str) else node.as_string()) But maybe some speed data comparaison will make you prefer my proposition: >>> import timeit
>>> timeit.timeit('literal_eval(node)', setup='import json;from pathlib import Path;from astroid.helpers import literal_eval;from astroid.builder import extract_node;node = extract_node(repr(json.load(Path("./10kSamplejson.json").open("r"))))', number=20)
0.9220764310000007
>>> timeit.timeit('_dummy_literal_eval(node)', setup='import json;from pathlib import Path;from astroid.helpers import _dummy_literal_eval;from astroid.builder import extract_node;node = extract_node(repr(json.load(Path("./10kSamplejson.json").open("r"))))', number=20)
7.558820483999995 I'v used this big testing json: the JSON file is here https://docs.google.com/uc?export=download&id=1RAp9w12hZkmLt2vJcAjdT-UtC3KpFyq4 |
I'd like someone else's opinion on this. I'm -0.5 to it. Obviously this would be a performance improvement and it might make sense to add this. However, since we're using this only in one place ourselves and we're basically copying the function from (Thanks @tristanlatr for the ping, I should have made this clearer 😄) |
Opened #1361 to discuss this. |
if node.func.name == "set": | ||
return set() | ||
if node.func.name == "tuple": | ||
return tuple() | ||
if node.func.name == "list": | ||
return list() | ||
if node.func.name == "dict": | ||
return dict() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if node.func.name == "set": | |
return set() | |
if node.func.name == "tuple": | |
return tuple() | |
if node.func.name == "list": | |
return list() | |
if node.func.name == "dict": | |
return dict() | |
if node.func.name == "set": | |
return set() | |
elif node.func.name == "tuple": | |
return tuple() | |
elif node.func.name == "list": | |
return list() | |
elif node.func.name == "dict": | |
return dict() | |
else: | |
_raise_malformed_node(node) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm with @DanielNoord on this on. It isn't clear to me why this would be useful. Even if it provides a small performance benefit, the cost of maintaining it is higher IMO. Especially considering literal_eval
is only used in the inference of Compare
nodes. With that kind of limited scope, it's unlikely that we would see any performance improvements.
If you do have a use-case for it, I would be interested to hear it. Maybe as response here? #1361 (comment)
I would like to close this one. As I explained in #1361 (comment), I don't think the helper would provide value for astroid at the current time. Furthermore, it would increase the maintenance burden if a new Python version is released and something changes. IMO every time there is an alternative in the stdlib, it's better to use that than to implement your own version for it. In this case the workaround with |
Seeing as this discussion seems to have died down I think I'm going to close this. There seems to be somewhat of a consensus (from the maintainer's side) that this is not something we would want to add right now. |
Provide actual
literal_eval()
function. Adapted from standard library's.Use that function in
inference.py
instead of the dummy_to_literal()
function.Type of Changes