add spec and rpm
This commit is contained in:
parent
222c399103
commit
820358c1d5
BIN
pure_eval-0.2.2.tar.gz
Normal file
BIN
pure_eval-0.2.2.tar.gz
Normal file
Binary file not shown.
693
python-pure-eval.spec
Normal file
693
python-pure-eval.spec
Normal file
@ -0,0 +1,693 @@
|
|||||||
|
%global _empty_manifest_terminate_build 0
|
||||||
|
Name: python-pure-eval
|
||||||
|
Version: 0.2.2
|
||||||
|
Release: 1
|
||||||
|
Summary: Safely evaluate AST nodes without side effects
|
||||||
|
License: MIT
|
||||||
|
URL: http://github.com/alexmojaki/pure_eval
|
||||||
|
Source0: https://files.pythonhosted.org/packages/97/5a/0bc937c25d3ce4e0a74335222aee05455d6afa2888032185f8ab50cdf6fd/pure_eval-0.2.2.tar.gz
|
||||||
|
BuildArch: noarch
|
||||||
|
|
||||||
|
Requires: python3-pytest
|
||||||
|
|
||||||
|
%description
|
||||||
|
# `pure_eval`
|
||||||
|
|
||||||
|
[](https://travis-ci.org/alexmojaki/pure_eval) [](https://coveralls.io/github/alexmojaki/pure_eval?branch=master) [](https://pypi.python.org/pypi/pure_eval)
|
||||||
|
|
||||||
|
This is a Python package that lets you safely evaluate certain AST nodes without triggering arbitrary code that may have unwanted side effects.
|
||||||
|
|
||||||
|
It can be installed from PyPI:
|
||||||
|
|
||||||
|
pip install pure_eval
|
||||||
|
|
||||||
|
To demonstrate usage, suppose we have an object defined as follows:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Rectangle:
|
||||||
|
def __init__(self, width, height):
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
|
||||||
|
@property
|
||||||
|
def area(self):
|
||||||
|
print("Calculating area...")
|
||||||
|
return self.width * self.height
|
||||||
|
|
||||||
|
|
||||||
|
rect = Rectangle(3, 5)
|
||||||
|
```
|
||||||
|
|
||||||
|
Given the `rect` object, we want to evaluate whatever expressions we can in this source code:
|
||||||
|
|
||||||
|
```python
|
||||||
|
source = "(rect.width, rect.height, rect.area)"
|
||||||
|
```
|
||||||
|
|
||||||
|
This library works with the AST, so let's parse the source code and peek inside:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import ast
|
||||||
|
|
||||||
|
tree = ast.parse(source)
|
||||||
|
the_tuple = tree.body[0].value
|
||||||
|
for node in the_tuple.elts:
|
||||||
|
print(ast.dump(node))
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```python
|
||||||
|
Attribute(value=Name(id='rect', ctx=Load()), attr='width', ctx=Load())
|
||||||
|
Attribute(value=Name(id='rect', ctx=Load()), attr='height', ctx=Load())
|
||||||
|
Attribute(value=Name(id='rect', ctx=Load()), attr='area', ctx=Load())
|
||||||
|
```
|
||||||
|
|
||||||
|
Now to actually use the library. First construct an Evaluator:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from pure_eval import Evaluator
|
||||||
|
|
||||||
|
evaluator = Evaluator({"rect": rect})
|
||||||
|
```
|
||||||
|
|
||||||
|
The argument to `Evaluator` should be a mapping from variable names to their values. Or if you have access to the stack frame where `rect` is defined, you can instead use:
|
||||||
|
|
||||||
|
```python
|
||||||
|
evaluator = Evaluator.from_frame(frame)
|
||||||
|
```
|
||||||
|
|
||||||
|
Now to evaluate some nodes, using `evaluator[node]`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
print("rect.width:", evaluator[the_tuple.elts[0]])
|
||||||
|
print("rect:", evaluator[the_tuple.elts[0].value])
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```
|
||||||
|
rect.width: 3
|
||||||
|
rect: <__main__.Rectangle object at 0x105b0dd30>
|
||||||
|
```
|
||||||
|
|
||||||
|
OK, but you could have done the same thing with `eval`. The useful part is that it will refuse to evaluate the property `rect.area` because that would trigger unknown code. If we try, it'll raise a `CannotEval` exception.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from pure_eval import CannotEval
|
||||||
|
|
||||||
|
try:
|
||||||
|
print("rect.area:", evaluator[the_tuple.elts[2]]) # fails
|
||||||
|
except CannotEval as e:
|
||||||
|
print(e) # prints CannotEval
|
||||||
|
```
|
||||||
|
|
||||||
|
To find all the expressions that can be evaluated in a tree:
|
||||||
|
|
||||||
|
```python
|
||||||
|
for node, value in evaluator.find_expressions(tree):
|
||||||
|
print(ast.dump(node), value)
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```python
|
||||||
|
Attribute(value=Name(id='rect', ctx=Load()), attr='width', ctx=Load()) 3
|
||||||
|
Attribute(value=Name(id='rect', ctx=Load()), attr='height', ctx=Load()) 5
|
||||||
|
Name(id='rect', ctx=Load()) <__main__.Rectangle object at 0x105568d30>
|
||||||
|
Name(id='rect', ctx=Load()) <__main__.Rectangle object at 0x105568d30>
|
||||||
|
Name(id='rect', ctx=Load()) <__main__.Rectangle object at 0x105568d30>
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that this includes `rect` three times, once for each appearance in the source code. Since all these nodes are equivalent, we can group them together:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from pure_eval import group_expressions
|
||||||
|
|
||||||
|
for nodes, values in group_expressions(evaluator.find_expressions(tree)):
|
||||||
|
print(len(nodes), "nodes with value:", values)
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```
|
||||||
|
1 nodes with value: 3
|
||||||
|
1 nodes with value: 5
|
||||||
|
3 nodes with value: <__main__.Rectangle object at 0x10d374d30>
|
||||||
|
```
|
||||||
|
|
||||||
|
If we want to list all the expressions in a tree, we may want to filter out certain expressions whose values are obvious. For example, suppose we have a function `foo`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def foo():
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
If we refer to `foo` by its name as usual, then that's not interesting:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from pure_eval import is_expression_interesting
|
||||||
|
|
||||||
|
node = ast.parse('foo').body[0].value
|
||||||
|
print(ast.dump(node))
|
||||||
|
print(is_expression_interesting(node, foo))
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```python
|
||||||
|
Name(id='foo', ctx=Load())
|
||||||
|
False
|
||||||
|
```
|
||||||
|
|
||||||
|
But if we refer to it by a different name, then it's interesting:
|
||||||
|
|
||||||
|
```python
|
||||||
|
node = ast.parse('bar').body[0].value
|
||||||
|
print(ast.dump(node))
|
||||||
|
print(is_expression_interesting(node, foo))
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```python
|
||||||
|
Name(id='bar', ctx=Load())
|
||||||
|
True
|
||||||
|
```
|
||||||
|
|
||||||
|
In general `is_expression_interesting` returns False for the following values:
|
||||||
|
- Literals (e.g. `123`, `'abc'`, `[1, 2, 3]`, `{'a': (), 'b': ([1, 2], [3])}`)
|
||||||
|
- Variables or attributes whose name is equal to the value's `__name__`, such as `foo` above or `self.foo` if it was a method.
|
||||||
|
- Builtins (e.g. `len`) referred to by their usual name.
|
||||||
|
|
||||||
|
To make things easier, you can combine finding expressions, grouping them, and filtering out the obvious ones with:
|
||||||
|
|
||||||
|
```python
|
||||||
|
evaluator.interesting_expressions_grouped(root)
|
||||||
|
```
|
||||||
|
|
||||||
|
To get the source code of an AST node, I recommend [asttokens](https://github.com/gristlabs/asttokens).
|
||||||
|
|
||||||
|
Here's a complete example that brings it all together:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from asttokens import ASTTokens
|
||||||
|
from pure_eval import Evaluator
|
||||||
|
|
||||||
|
source = """
|
||||||
|
x = 1
|
||||||
|
d = {x: 2}
|
||||||
|
y = d[x]
|
||||||
|
"""
|
||||||
|
|
||||||
|
names = {}
|
||||||
|
exec(source, names)
|
||||||
|
atok = ASTTokens(source, parse=True)
|
||||||
|
for nodes, value in Evaluator(names).interesting_expressions_grouped(atok.tree):
|
||||||
|
print(atok.get_text(nodes[0]), "=", value)
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```python
|
||||||
|
x = 1
|
||||||
|
d = {1: 2}
|
||||||
|
y = 2
|
||||||
|
d[x] = 2
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%package -n python3-pure-eval
|
||||||
|
Summary: Safely evaluate AST nodes without side effects
|
||||||
|
Provides: python-pure-eval
|
||||||
|
BuildRequires: python3-devel
|
||||||
|
BuildRequires: python3-setuptools
|
||||||
|
BuildRequires: python3-pip
|
||||||
|
BuildRequires: python3-setuptools_scm
|
||||||
|
BuildRequires: python3-toml
|
||||||
|
%description -n python3-pure-eval
|
||||||
|
# `pure_eval`
|
||||||
|
|
||||||
|
[](https://travis-ci.org/alexmojaki/pure_eval) [](https://coveralls.io/github/alexmojaki/pure_eval?branch=master) [](https://pypi.python.org/pypi/pure_eval)
|
||||||
|
|
||||||
|
This is a Python package that lets you safely evaluate certain AST nodes without triggering arbitrary code that may have unwanted side effects.
|
||||||
|
|
||||||
|
It can be installed from PyPI:
|
||||||
|
|
||||||
|
pip install pure_eval
|
||||||
|
|
||||||
|
To demonstrate usage, suppose we have an object defined as follows:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Rectangle:
|
||||||
|
def __init__(self, width, height):
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
|
||||||
|
@property
|
||||||
|
def area(self):
|
||||||
|
print("Calculating area...")
|
||||||
|
return self.width * self.height
|
||||||
|
|
||||||
|
|
||||||
|
rect = Rectangle(3, 5)
|
||||||
|
```
|
||||||
|
|
||||||
|
Given the `rect` object, we want to evaluate whatever expressions we can in this source code:
|
||||||
|
|
||||||
|
```python
|
||||||
|
source = "(rect.width, rect.height, rect.area)"
|
||||||
|
```
|
||||||
|
|
||||||
|
This library works with the AST, so let's parse the source code and peek inside:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import ast
|
||||||
|
|
||||||
|
tree = ast.parse(source)
|
||||||
|
the_tuple = tree.body[0].value
|
||||||
|
for node in the_tuple.elts:
|
||||||
|
print(ast.dump(node))
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```python
|
||||||
|
Attribute(value=Name(id='rect', ctx=Load()), attr='width', ctx=Load())
|
||||||
|
Attribute(value=Name(id='rect', ctx=Load()), attr='height', ctx=Load())
|
||||||
|
Attribute(value=Name(id='rect', ctx=Load()), attr='area', ctx=Load())
|
||||||
|
```
|
||||||
|
|
||||||
|
Now to actually use the library. First construct an Evaluator:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from pure_eval import Evaluator
|
||||||
|
|
||||||
|
evaluator = Evaluator({"rect": rect})
|
||||||
|
```
|
||||||
|
|
||||||
|
The argument to `Evaluator` should be a mapping from variable names to their values. Or if you have access to the stack frame where `rect` is defined, you can instead use:
|
||||||
|
|
||||||
|
```python
|
||||||
|
evaluator = Evaluator.from_frame(frame)
|
||||||
|
```
|
||||||
|
|
||||||
|
Now to evaluate some nodes, using `evaluator[node]`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
print("rect.width:", evaluator[the_tuple.elts[0]])
|
||||||
|
print("rect:", evaluator[the_tuple.elts[0].value])
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```
|
||||||
|
rect.width: 3
|
||||||
|
rect: <__main__.Rectangle object at 0x105b0dd30>
|
||||||
|
```
|
||||||
|
|
||||||
|
OK, but you could have done the same thing with `eval`. The useful part is that it will refuse to evaluate the property `rect.area` because that would trigger unknown code. If we try, it'll raise a `CannotEval` exception.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from pure_eval import CannotEval
|
||||||
|
|
||||||
|
try:
|
||||||
|
print("rect.area:", evaluator[the_tuple.elts[2]]) # fails
|
||||||
|
except CannotEval as e:
|
||||||
|
print(e) # prints CannotEval
|
||||||
|
```
|
||||||
|
|
||||||
|
To find all the expressions that can be evaluated in a tree:
|
||||||
|
|
||||||
|
```python
|
||||||
|
for node, value in evaluator.find_expressions(tree):
|
||||||
|
print(ast.dump(node), value)
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```python
|
||||||
|
Attribute(value=Name(id='rect', ctx=Load()), attr='width', ctx=Load()) 3
|
||||||
|
Attribute(value=Name(id='rect', ctx=Load()), attr='height', ctx=Load()) 5
|
||||||
|
Name(id='rect', ctx=Load()) <__main__.Rectangle object at 0x105568d30>
|
||||||
|
Name(id='rect', ctx=Load()) <__main__.Rectangle object at 0x105568d30>
|
||||||
|
Name(id='rect', ctx=Load()) <__main__.Rectangle object at 0x105568d30>
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that this includes `rect` three times, once for each appearance in the source code. Since all these nodes are equivalent, we can group them together:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from pure_eval import group_expressions
|
||||||
|
|
||||||
|
for nodes, values in group_expressions(evaluator.find_expressions(tree)):
|
||||||
|
print(len(nodes), "nodes with value:", values)
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```
|
||||||
|
1 nodes with value: 3
|
||||||
|
1 nodes with value: 5
|
||||||
|
3 nodes with value: <__main__.Rectangle object at 0x10d374d30>
|
||||||
|
```
|
||||||
|
|
||||||
|
If we want to list all the expressions in a tree, we may want to filter out certain expressions whose values are obvious. For example, suppose we have a function `foo`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def foo():
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
If we refer to `foo` by its name as usual, then that's not interesting:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from pure_eval import is_expression_interesting
|
||||||
|
|
||||||
|
node = ast.parse('foo').body[0].value
|
||||||
|
print(ast.dump(node))
|
||||||
|
print(is_expression_interesting(node, foo))
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```python
|
||||||
|
Name(id='foo', ctx=Load())
|
||||||
|
False
|
||||||
|
```
|
||||||
|
|
||||||
|
But if we refer to it by a different name, then it's interesting:
|
||||||
|
|
||||||
|
```python
|
||||||
|
node = ast.parse('bar').body[0].value
|
||||||
|
print(ast.dump(node))
|
||||||
|
print(is_expression_interesting(node, foo))
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```python
|
||||||
|
Name(id='bar', ctx=Load())
|
||||||
|
True
|
||||||
|
```
|
||||||
|
|
||||||
|
In general `is_expression_interesting` returns False for the following values:
|
||||||
|
- Literals (e.g. `123`, `'abc'`, `[1, 2, 3]`, `{'a': (), 'b': ([1, 2], [3])}`)
|
||||||
|
- Variables or attributes whose name is equal to the value's `__name__`, such as `foo` above or `self.foo` if it was a method.
|
||||||
|
- Builtins (e.g. `len`) referred to by their usual name.
|
||||||
|
|
||||||
|
To make things easier, you can combine finding expressions, grouping them, and filtering out the obvious ones with:
|
||||||
|
|
||||||
|
```python
|
||||||
|
evaluator.interesting_expressions_grouped(root)
|
||||||
|
```
|
||||||
|
|
||||||
|
To get the source code of an AST node, I recommend [asttokens](https://github.com/gristlabs/asttokens).
|
||||||
|
|
||||||
|
Here's a complete example that brings it all together:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from asttokens import ASTTokens
|
||||||
|
from pure_eval import Evaluator
|
||||||
|
|
||||||
|
source = """
|
||||||
|
x = 1
|
||||||
|
d = {x: 2}
|
||||||
|
y = d[x]
|
||||||
|
"""
|
||||||
|
|
||||||
|
names = {}
|
||||||
|
exec(source, names)
|
||||||
|
atok = ASTTokens(source, parse=True)
|
||||||
|
for nodes, value in Evaluator(names).interesting_expressions_grouped(atok.tree):
|
||||||
|
print(atok.get_text(nodes[0]), "=", value)
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```python
|
||||||
|
x = 1
|
||||||
|
d = {1: 2}
|
||||||
|
y = 2
|
||||||
|
d[x] = 2
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%package help
|
||||||
|
Summary: Development documents and examples for pure-eval
|
||||||
|
Provides: python3-pure-eval-doc
|
||||||
|
%description help
|
||||||
|
# `pure_eval`
|
||||||
|
|
||||||
|
[](https://travis-ci.org/alexmojaki/pure_eval) [](https://coveralls.io/github/alexmojaki/pure_eval?branch=master) [](https://pypi.python.org/pypi/pure_eval)
|
||||||
|
|
||||||
|
This is a Python package that lets you safely evaluate certain AST nodes without triggering arbitrary code that may have unwanted side effects.
|
||||||
|
|
||||||
|
It can be installed from PyPI:
|
||||||
|
|
||||||
|
pip install pure_eval
|
||||||
|
|
||||||
|
To demonstrate usage, suppose we have an object defined as follows:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Rectangle:
|
||||||
|
def __init__(self, width, height):
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
|
||||||
|
@property
|
||||||
|
def area(self):
|
||||||
|
print("Calculating area...")
|
||||||
|
return self.width * self.height
|
||||||
|
|
||||||
|
|
||||||
|
rect = Rectangle(3, 5)
|
||||||
|
```
|
||||||
|
|
||||||
|
Given the `rect` object, we want to evaluate whatever expressions we can in this source code:
|
||||||
|
|
||||||
|
```python
|
||||||
|
source = "(rect.width, rect.height, rect.area)"
|
||||||
|
```
|
||||||
|
|
||||||
|
This library works with the AST, so let's parse the source code and peek inside:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import ast
|
||||||
|
|
||||||
|
tree = ast.parse(source)
|
||||||
|
the_tuple = tree.body[0].value
|
||||||
|
for node in the_tuple.elts:
|
||||||
|
print(ast.dump(node))
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```python
|
||||||
|
Attribute(value=Name(id='rect', ctx=Load()), attr='width', ctx=Load())
|
||||||
|
Attribute(value=Name(id='rect', ctx=Load()), attr='height', ctx=Load())
|
||||||
|
Attribute(value=Name(id='rect', ctx=Load()), attr='area', ctx=Load())
|
||||||
|
```
|
||||||
|
|
||||||
|
Now to actually use the library. First construct an Evaluator:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from pure_eval import Evaluator
|
||||||
|
|
||||||
|
evaluator = Evaluator({"rect": rect})
|
||||||
|
```
|
||||||
|
|
||||||
|
The argument to `Evaluator` should be a mapping from variable names to their values. Or if you have access to the stack frame where `rect` is defined, you can instead use:
|
||||||
|
|
||||||
|
```python
|
||||||
|
evaluator = Evaluator.from_frame(frame)
|
||||||
|
```
|
||||||
|
|
||||||
|
Now to evaluate some nodes, using `evaluator[node]`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
print("rect.width:", evaluator[the_tuple.elts[0]])
|
||||||
|
print("rect:", evaluator[the_tuple.elts[0].value])
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```
|
||||||
|
rect.width: 3
|
||||||
|
rect: <__main__.Rectangle object at 0x105b0dd30>
|
||||||
|
```
|
||||||
|
|
||||||
|
OK, but you could have done the same thing with `eval`. The useful part is that it will refuse to evaluate the property `rect.area` because that would trigger unknown code. If we try, it'll raise a `CannotEval` exception.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from pure_eval import CannotEval
|
||||||
|
|
||||||
|
try:
|
||||||
|
print("rect.area:", evaluator[the_tuple.elts[2]]) # fails
|
||||||
|
except CannotEval as e:
|
||||||
|
print(e) # prints CannotEval
|
||||||
|
```
|
||||||
|
|
||||||
|
To find all the expressions that can be evaluated in a tree:
|
||||||
|
|
||||||
|
```python
|
||||||
|
for node, value in evaluator.find_expressions(tree):
|
||||||
|
print(ast.dump(node), value)
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```python
|
||||||
|
Attribute(value=Name(id='rect', ctx=Load()), attr='width', ctx=Load()) 3
|
||||||
|
Attribute(value=Name(id='rect', ctx=Load()), attr='height', ctx=Load()) 5
|
||||||
|
Name(id='rect', ctx=Load()) <__main__.Rectangle object at 0x105568d30>
|
||||||
|
Name(id='rect', ctx=Load()) <__main__.Rectangle object at 0x105568d30>
|
||||||
|
Name(id='rect', ctx=Load()) <__main__.Rectangle object at 0x105568d30>
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that this includes `rect` three times, once for each appearance in the source code. Since all these nodes are equivalent, we can group them together:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from pure_eval import group_expressions
|
||||||
|
|
||||||
|
for nodes, values in group_expressions(evaluator.find_expressions(tree)):
|
||||||
|
print(len(nodes), "nodes with value:", values)
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```
|
||||||
|
1 nodes with value: 3
|
||||||
|
1 nodes with value: 5
|
||||||
|
3 nodes with value: <__main__.Rectangle object at 0x10d374d30>
|
||||||
|
```
|
||||||
|
|
||||||
|
If we want to list all the expressions in a tree, we may want to filter out certain expressions whose values are obvious. For example, suppose we have a function `foo`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def foo():
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
If we refer to `foo` by its name as usual, then that's not interesting:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from pure_eval import is_expression_interesting
|
||||||
|
|
||||||
|
node = ast.parse('foo').body[0].value
|
||||||
|
print(ast.dump(node))
|
||||||
|
print(is_expression_interesting(node, foo))
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```python
|
||||||
|
Name(id='foo', ctx=Load())
|
||||||
|
False
|
||||||
|
```
|
||||||
|
|
||||||
|
But if we refer to it by a different name, then it's interesting:
|
||||||
|
|
||||||
|
```python
|
||||||
|
node = ast.parse('bar').body[0].value
|
||||||
|
print(ast.dump(node))
|
||||||
|
print(is_expression_interesting(node, foo))
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```python
|
||||||
|
Name(id='bar', ctx=Load())
|
||||||
|
True
|
||||||
|
```
|
||||||
|
|
||||||
|
In general `is_expression_interesting` returns False for the following values:
|
||||||
|
- Literals (e.g. `123`, `'abc'`, `[1, 2, 3]`, `{'a': (), 'b': ([1, 2], [3])}`)
|
||||||
|
- Variables or attributes whose name is equal to the value's `__name__`, such as `foo` above or `self.foo` if it was a method.
|
||||||
|
- Builtins (e.g. `len`) referred to by their usual name.
|
||||||
|
|
||||||
|
To make things easier, you can combine finding expressions, grouping them, and filtering out the obvious ones with:
|
||||||
|
|
||||||
|
```python
|
||||||
|
evaluator.interesting_expressions_grouped(root)
|
||||||
|
```
|
||||||
|
|
||||||
|
To get the source code of an AST node, I recommend [asttokens](https://github.com/gristlabs/asttokens).
|
||||||
|
|
||||||
|
Here's a complete example that brings it all together:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from asttokens import ASTTokens
|
||||||
|
from pure_eval import Evaluator
|
||||||
|
|
||||||
|
source = """
|
||||||
|
x = 1
|
||||||
|
d = {x: 2}
|
||||||
|
y = d[x]
|
||||||
|
"""
|
||||||
|
|
||||||
|
names = {}
|
||||||
|
exec(source, names)
|
||||||
|
atok = ASTTokens(source, parse=True)
|
||||||
|
for nodes, value in Evaluator(names).interesting_expressions_grouped(atok.tree):
|
||||||
|
print(atok.get_text(nodes[0]), "=", value)
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```python
|
||||||
|
x = 1
|
||||||
|
d = {1: 2}
|
||||||
|
y = 2
|
||||||
|
d[x] = 2
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%prep
|
||||||
|
%autosetup -n pure_eval-0.2.2
|
||||||
|
|
||||||
|
%build
|
||||||
|
%py3_build
|
||||||
|
|
||||||
|
%install
|
||||||
|
%py3_install
|
||||||
|
install -d -m755 %{buildroot}/%{_pkgdocdir}
|
||||||
|
if [ -d doc ]; then cp -arf doc %{buildroot}/%{_pkgdocdir}; fi
|
||||||
|
if [ -d docs ]; then cp -arf docs %{buildroot}/%{_pkgdocdir}; fi
|
||||||
|
if [ -d example ]; then cp -arf example %{buildroot}/%{_pkgdocdir}; fi
|
||||||
|
if [ -d examples ]; then cp -arf examples %{buildroot}/%{_pkgdocdir}; fi
|
||||||
|
pushd %{buildroot}
|
||||||
|
if [ -d usr/lib ]; then
|
||||||
|
find usr/lib -type f -printf "\"/%h/%f\"\n" >> filelist.lst
|
||||||
|
fi
|
||||||
|
if [ -d usr/lib64 ]; then
|
||||||
|
find usr/lib64 -type f -printf "\"/%h/%f\"\n" >> filelist.lst
|
||||||
|
fi
|
||||||
|
if [ -d usr/bin ]; then
|
||||||
|
find usr/bin -type f -printf "\"/%h/%f\"\n" >> filelist.lst
|
||||||
|
fi
|
||||||
|
if [ -d usr/sbin ]; then
|
||||||
|
find usr/sbin -type f -printf "\"/%h/%f\"\n" >> filelist.lst
|
||||||
|
fi
|
||||||
|
touch doclist.lst
|
||||||
|
if [ -d usr/share/man ]; then
|
||||||
|
find usr/share/man -type f -printf "\"/%h/%f.gz\"\n" >> doclist.lst
|
||||||
|
fi
|
||||||
|
popd
|
||||||
|
mv %{buildroot}/filelist.lst .
|
||||||
|
mv %{buildroot}/doclist.lst .
|
||||||
|
|
||||||
|
%files -n python3-pure-eval -f filelist.lst
|
||||||
|
%dir %{python3_sitelib}/*
|
||||||
|
|
||||||
|
%files help -f doclist.lst
|
||||||
|
%{_docdir}/*
|
||||||
|
|
||||||
|
%changelog
|
||||||
|
* Fri May 19 2023 Python_Bot <Python_Bot@openeuler.org> - 0.2.2-1
|
||||||
|
- Package Spec generated
|
||||||
Loading…
x
Reference in New Issue
Block a user