commit
697ea796dc
@ -0,0 +1 @@
|
||||
SOURCES/numexpr-2.8.5.tar.gz
|
@ -0,0 +1 @@
|
||||
fbd681371c05653c3595149bf25102d5b806b2b6 SOURCES/numexpr-2.8.5.tar.gz
|
@ -0,0 +1,254 @@
|
||||
From be21651a1fada038b8ca00938d063fbb3336b989 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
|
||||
Date: Wed, 16 Aug 2023 17:23:26 +0200
|
||||
Subject: [PATCH 2/7] Revert "Make more difficult sanitize of the expression
|
||||
string before eval"
|
||||
|
||||
This reverts commit 00b035c78ca5ac209b58b56b5dcc99596cac423c.
|
||||
---
|
||||
ANNOUNCE.rst | 23 ++---------------------
|
||||
RELEASE_NOTES.rst | 19 +------------------
|
||||
doc/user_guide.rst | 27 ++++++++++-----------------
|
||||
numexpr/necompiler.py | 27 ++++++++-------------------
|
||||
numexpr/tests/test_numexpr.py | 18 ++----------------
|
||||
5 files changed, 23 insertions(+), 91 deletions(-)
|
||||
|
||||
diff --git a/ANNOUNCE.rst b/ANNOUNCE.rst
|
||||
index 4e9070f2cc..f038df4f44 100644
|
||||
--- a/ANNOUNCE.rst
|
||||
+++ b/ANNOUNCE.rst
|
||||
@@ -4,10 +4,7 @@ Announcing NumExpr 2.8.5
|
||||
|
||||
Hi everyone,
|
||||
|
||||
-In 2.8.5 we have added a new function, `validate` which checks an expression `ex`
|
||||
-for validity, for usage where the program is parsing a user input. There are also
|
||||
-consequences for this sort of usage, since `eval(ex)` is called, and as such we
|
||||
-do some string sanitization as described below.
|
||||
+**Under development.**
|
||||
|
||||
Project documentation is available at:
|
||||
|
||||
@@ -16,23 +13,7 @@ http://numexpr.readthedocs.io/
|
||||
Changes from 2.8.4 to 2.8.5
|
||||
---------------------------
|
||||
|
||||
-* A `validate` function has been added. This function checks the inputs, returning
|
||||
- `None` on success or raising an exception on invalid inputs. This function was
|
||||
- added as numerous projects seem to be using NumExpr for parsing user inputs.
|
||||
- `re_evaluate` may be called directly following `validate`.
|
||||
-* As an addendum to the use of NumExpr for parsing user inputs, is that NumExpr
|
||||
- calls `eval` on the inputs. A regular expression is now applied to help sanitize
|
||||
- the input expression string, forbidding '__', ':', and ';'. Attribute access
|
||||
- is also banned except for '.r' for real and '.i' for imag.
|
||||
-* Thanks to timbrist for a fix to behavior of NumExpr with integers to negative
|
||||
- powers. NumExpr was pre-checking integer powers for negative values, which
|
||||
- was both inefficient and causing parsing errors in some situations. Now NumExpr
|
||||
- will simply return 0 as a result for such cases. While NumExpr generally tries
|
||||
- to follow NumPy behavior, performance is also critical.
|
||||
-* Thanks to peadar for some fixes to how NumExpr launches threads for embedded
|
||||
- applications.
|
||||
-* Thanks to de11n for making parsing of the `site.cfg` for MKL consistent among
|
||||
- all shared platforms.
|
||||
+**Under development.**
|
||||
|
||||
|
||||
What's Numexpr?
|
||||
diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst
|
||||
index 9cf5d3977b..4929a42e12 100644
|
||||
--- a/RELEASE_NOTES.rst
|
||||
+++ b/RELEASE_NOTES.rst
|
||||
@@ -5,24 +5,7 @@ Release notes for NumExpr 2.8 series
|
||||
Changes from 2.8.4 to 2.8.5
|
||||
---------------------------
|
||||
|
||||
-* A `validate` function has been added. This function checks the inputs, returning
|
||||
- `None` on success or raising an exception on invalid inputs. This function was
|
||||
- added as numerous projects seem to be using NumExpr for parsing user inputs.
|
||||
- `re_evaluate` may be called directly following `validate`.
|
||||
-* As an addendum to the use of NumExpr for parsing user inputs, is that NumExpr
|
||||
- calls `eval` on the inputs. A regular expression is now applied to help sanitize
|
||||
- the input expression string, forbidding '__', ':', and ';'. Attribute access
|
||||
- is also banned except for '.r' for real and '.i' for imag.
|
||||
-* Thanks to timbrist for a fix to behavior of NumExpr with integers to negative
|
||||
- powers. NumExpr was pre-checking integer powers for negative values, which
|
||||
- was both inefficient and causing parsing errors in some situations. Now NumExpr
|
||||
- will simply return 0 as a result for such cases. While NumExpr generally tries
|
||||
- to follow NumPy behavior, performance is also critical.
|
||||
-* Thanks to peadar for some fixes to how NumExpr launches threads for embedded
|
||||
- applications.
|
||||
-* Thanks to de11n for making parsing of the `site.cfg` for MKL consistent among
|
||||
- all shared platforms.
|
||||
-
|
||||
+**Under development.**
|
||||
|
||||
Changes from 2.8.3 to 2.8.4
|
||||
---------------------------
|
||||
diff --git a/doc/user_guide.rst b/doc/user_guide.rst
|
||||
index 3a3cf63d9c..74306eb658 100644
|
||||
--- a/doc/user_guide.rst
|
||||
+++ b/doc/user_guide.rst
|
||||
@@ -1,7 +1,7 @@
|
||||
-NumExpr 2.8 User Guide
|
||||
+NumExpr 2.0 User Guide
|
||||
======================
|
||||
|
||||
-The NumExpr package supplies routines for the fast evaluation of
|
||||
+The :code:`numexpr` package supplies routines for the fast evaluation of
|
||||
array expressions elementwise by using a vector-based virtual
|
||||
machine.
|
||||
|
||||
@@ -11,33 +11,23 @@ Using it is simple::
|
||||
>>> import numexpr as ne
|
||||
>>> a = np.arange(10)
|
||||
>>> b = np.arange(0, 20, 2)
|
||||
- >>> c = ne.evaluate('2*a + 3*b')
|
||||
+ >>> c = ne.evaluate("2*a+3*b")
|
||||
>>> c
|
||||
array([ 0, 8, 16, 24, 32, 40, 48, 56, 64, 72])
|
||||
|
||||
|
||||
-It is also possible to use NumExpr to validate an expression::
|
||||
-
|
||||
- >>> ne.validate('2*a + 3*b')
|
||||
-
|
||||
-which returns `None` on success or raises an exception on invalid inputs.
|
||||
-
|
||||
-and it can also re_evaluate an expression::
|
||||
-
|
||||
- >>> b = np.arange(0, 40, 4)
|
||||
- >>> ne.re_evaluate()
|
||||
-
|
||||
Building
|
||||
--------
|
||||
|
||||
-*NumExpr* requires Python_ 3.7 or greater, and NumPy_ 1.13 or greater. It is
|
||||
+*NumExpr* requires Python_ 2.6 or greater, and NumPy_ 1.7 or greater. It is
|
||||
built in the standard Python way:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
- $ pip install .
|
||||
+ $ python setup.py build
|
||||
+ $ python setup.py install
|
||||
|
||||
-You must have a C-compiler (i.e. MSVC Build tools on Windows and GCC on Linux) installed.
|
||||
+You must have a C-compiler (i.e. MSVC on Windows and GCC on Linux) installed.
|
||||
|
||||
Then change to a directory that is not the repository directory (e.g. `/tmp`) and
|
||||
test :code:`numexpr` with:
|
||||
@@ -278,6 +268,9 @@ General routines
|
||||
* :code:`detect_number_of_cores()`: Detects the number of cores on a system.
|
||||
|
||||
|
||||
+
|
||||
+
|
||||
+
|
||||
Intel's VML specific support routines
|
||||
-------------------------------------
|
||||
|
||||
diff --git a/numexpr/necompiler.py b/numexpr/necompiler.py
|
||||
index cbf290932b..fef886baf5 100644
|
||||
--- a/numexpr/necompiler.py
|
||||
+++ b/numexpr/necompiler.py
|
||||
@@ -260,17 +260,15 @@ class Immediate(Register):
|
||||
def __str__(self):
|
||||
return 'Immediate(%d)' % (self.node.value,)
|
||||
|
||||
-
|
||||
-_forbidden_re = re.compile('[\;[\:]|__|\.[abcdefghjklmnopqstuvwxyzA-Z_]')
|
||||
+_forbidden_re = re.compile('[\;[\:]|__')
|
||||
def stringToExpression(s, types, context):
|
||||
"""Given a string, convert it to a tree of ExpressionNode's.
|
||||
"""
|
||||
# sanitize the string for obvious attack vectors that NumExpr cannot
|
||||
# parse into its homebrew AST. This is to protect the call to `eval` below.
|
||||
- # We forbid `;`, `:`. `[` and `__`, and attribute access via '.'.
|
||||
- # We cannot ban `.real` or `.imag` however...
|
||||
- no_whitespace = re.sub(r'\s+', '', s)
|
||||
- if _forbidden_re.search(no_whitespace) is not None:
|
||||
+ # We forbid `;`, `:`. `[` and `__`
|
||||
+ # We would like to forbid `.` but it is both a reference and decimal point.
|
||||
+ if _forbidden_re.search(s) is not None:
|
||||
raise ValueError(f'Expression {s} has forbidden control characters.')
|
||||
|
||||
old_ctx = expressions._context.get_current_context()
|
||||
@@ -768,6 +766,7 @@ def getArguments(names, local_dict=None, global_dict=None, _frame_depth: int=2):
|
||||
_names_cache = CacheDict(256)
|
||||
_numexpr_cache = CacheDict(256)
|
||||
_numexpr_last = {}
|
||||
+_numexpr_sanity = set()
|
||||
evaluate_lock = threading.Lock()
|
||||
|
||||
# MAYBE: decorate this function to add attributes instead of having the
|
||||
@@ -829,13 +828,6 @@ def validate(ex: str,
|
||||
_frame_depth: int
|
||||
The calling frame depth. Unless you are a NumExpr developer you should
|
||||
not set this value.
|
||||
-
|
||||
- Note
|
||||
- ----
|
||||
- Both `validate` and by extension `evaluate` call `eval(ex)`, which is
|
||||
- potentially dangerous on unsanitized inputs. As such, NumExpr does some
|
||||
- sanitization, banning the character ':;[', the dunder '__', and attribute
|
||||
- access to all but '.r' for real and '.i' for imag access to complex numbers.
|
||||
"""
|
||||
global _numexpr_last
|
||||
|
||||
@@ -865,6 +857,8 @@ def validate(ex: str,
|
||||
kwargs = {'out': out, 'order': order, 'casting': casting,
|
||||
'ex_uses_vml': ex_uses_vml}
|
||||
_numexpr_last = dict(ex=compiled_ex, argnames=names, kwargs=kwargs)
|
||||
+ # with evaluate_lock:
|
||||
+ # return compiled_ex(*arguments, **kwargs)
|
||||
except Exception as e:
|
||||
return e
|
||||
return None
|
||||
@@ -924,12 +918,7 @@ def evaluate(ex: str,
|
||||
The calling frame depth. Unless you are a NumExpr developer you should
|
||||
not set this value.
|
||||
|
||||
- Note
|
||||
- ----
|
||||
- Both `validate` and by extension `evaluate` call `eval(ex)`, which is
|
||||
- potentially dangerous on unsanitized inputs. As such, NumExpr does some
|
||||
- sanitization, banning the character ':;[', the dunder '__', and attribute
|
||||
- access to all but '.r' for real and '.i' for imag access to complex numbers.
|
||||
+
|
||||
"""
|
||||
# We could avoid code duplication if we called validate and then re_evaluate
|
||||
# here, but they we have difficulties with the `sys.getframe(2)` call in
|
||||
diff --git a/numexpr/tests/test_numexpr.py b/numexpr/tests/test_numexpr.py
|
||||
index a9f917fccd..ebc41c8d54 100644
|
||||
--- a/numexpr/tests/test_numexpr.py
|
||||
+++ b/numexpr/tests/test_numexpr.py
|
||||
@@ -536,27 +536,13 @@ class test_evaluate(TestCase):
|
||||
|
||||
# Forbid semicolon
|
||||
try:
|
||||
- evaluate('import os;')
|
||||
+ evaluate('import os; os.cpu_count()')
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
self.fail()
|
||||
|
||||
- # Attribute access
|
||||
- try:
|
||||
- evaluate('os.cpucount()')
|
||||
- except ValueError:
|
||||
- pass
|
||||
- else:
|
||||
- self.fail()
|
||||
-
|
||||
- # But decimal point must pass
|
||||
- a = 3.0
|
||||
- evaluate('a*2.')
|
||||
- evaluate('2.+a')
|
||||
-
|
||||
-
|
||||
-
|
||||
+ # I struggle to come up with cases for our ban on `'` and `"`
|
||||
|
||||
|
||||
|
@ -0,0 +1,183 @@
|
||||
From aa18c52226ddf3d5a3448702fc2b1a14b0ee3c2f Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
|
||||
Date: Wed, 16 Aug 2023 17:23:36 +0200
|
||||
Subject: [PATCH 3/7] Revert "Add in protections against call to
|
||||
`eval(expression)`"
|
||||
|
||||
This reverts commit 4b2d89cf14e75030d27629925b9998e1e91d23c7.
|
||||
---
|
||||
numexpr/necompiler.py | 26 +++++++-----------
|
||||
numexpr/tests/test_numexpr.py | 50 ++++-------------------------------
|
||||
2 files changed, 15 insertions(+), 61 deletions(-)
|
||||
|
||||
diff --git a/numexpr/necompiler.py b/numexpr/necompiler.py
|
||||
index fef886baf5..37052acadb 100644
|
||||
--- a/numexpr/necompiler.py
|
||||
+++ b/numexpr/necompiler.py
|
||||
@@ -13,7 +13,6 @@ import __future__
|
||||
import sys
|
||||
import numpy
|
||||
import threading
|
||||
-import re
|
||||
|
||||
is_cpu_amd_intel = False # DEPRECATION WARNING: WILL BE REMOVED IN FUTURE RELEASE
|
||||
from numexpr import interpreter, expressions, use_vml
|
||||
@@ -260,17 +259,10 @@ class Immediate(Register):
|
||||
def __str__(self):
|
||||
return 'Immediate(%d)' % (self.node.value,)
|
||||
|
||||
-_forbidden_re = re.compile('[\;[\:]|__')
|
||||
+
|
||||
def stringToExpression(s, types, context):
|
||||
"""Given a string, convert it to a tree of ExpressionNode's.
|
||||
"""
|
||||
- # sanitize the string for obvious attack vectors that NumExpr cannot
|
||||
- # parse into its homebrew AST. This is to protect the call to `eval` below.
|
||||
- # We forbid `;`, `:`. `[` and `__`
|
||||
- # We would like to forbid `.` but it is both a reference and decimal point.
|
||||
- if _forbidden_re.search(s) is not None:
|
||||
- raise ValueError(f'Expression {s} has forbidden control characters.')
|
||||
-
|
||||
old_ctx = expressions._context.get_current_context()
|
||||
try:
|
||||
expressions._context.set_new_context(context)
|
||||
@@ -293,10 +285,8 @@ def stringToExpression(s, types, context):
|
||||
t = types.get(name, default_type)
|
||||
names[name] = expressions.VariableNode(name, type_to_kind[t])
|
||||
names.update(expressions.functions)
|
||||
-
|
||||
# now build the expression
|
||||
ex = eval(c, names)
|
||||
-
|
||||
if expressions.isConstant(ex):
|
||||
ex = expressions.ConstantNode(ex, expressions.getKind(ex))
|
||||
elif not isinstance(ex, expressions.ExpressionNode):
|
||||
@@ -621,7 +611,9 @@ def NumExpr(ex, signature=(), **kwargs):
|
||||
|
||||
Returns a `NumExpr` object containing the compiled function.
|
||||
"""
|
||||
-
|
||||
+ # NumExpr can be called either directly by the end-user, in which case
|
||||
+ # kwargs need to be sanitized by getContext, or by evaluate,
|
||||
+ # in which case kwargs are in already sanitized.
|
||||
# In that case _frame_depth is wrong (it should be 2) but it doesn't matter
|
||||
# since it will not be used (because truediv='auto' has already been
|
||||
# translated to either True or False).
|
||||
@@ -766,7 +758,7 @@ def getArguments(names, local_dict=None, global_dict=None, _frame_depth: int=2):
|
||||
_names_cache = CacheDict(256)
|
||||
_numexpr_cache = CacheDict(256)
|
||||
_numexpr_last = {}
|
||||
-_numexpr_sanity = set()
|
||||
+
|
||||
evaluate_lock = threading.Lock()
|
||||
|
||||
# MAYBE: decorate this function to add attributes instead of having the
|
||||
@@ -869,7 +861,7 @@ def evaluate(ex: str,
|
||||
out: numpy.ndarray = None,
|
||||
order: str = 'K',
|
||||
casting: str = 'safe',
|
||||
- _frame_depth: int = 3,
|
||||
+ _frame_depth: int=3,
|
||||
**kwargs) -> numpy.ndarray:
|
||||
"""
|
||||
Evaluate a simple array expression element-wise using the virtual machine.
|
||||
@@ -917,8 +909,6 @@ def evaluate(ex: str,
|
||||
_frame_depth: int
|
||||
The calling frame depth. Unless you are a NumExpr developer you should
|
||||
not set this value.
|
||||
-
|
||||
-
|
||||
"""
|
||||
# We could avoid code duplication if we called validate and then re_evaluate
|
||||
# here, but they we have difficulties with the `sys.getframe(2)` call in
|
||||
@@ -931,6 +921,10 @@ def evaluate(ex: str,
|
||||
else:
|
||||
raise e
|
||||
|
||||
+
|
||||
+
|
||||
+
|
||||
+
|
||||
def re_evaluate(local_dict: Optional[Dict] = None,
|
||||
_frame_depth: int=2) -> numpy.ndarray:
|
||||
"""
|
||||
diff --git a/numexpr/tests/test_numexpr.py b/numexpr/tests/test_numexpr.py
|
||||
index ebc41c8d54..ccb0b6cb07 100644
|
||||
--- a/numexpr/tests/test_numexpr.py
|
||||
+++ b/numexpr/tests/test_numexpr.py
|
||||
@@ -373,9 +373,8 @@ class test_evaluate(TestCase):
|
||||
a1 = array([1., 2., 3.])
|
||||
b1 = array([4., 5., 6.])
|
||||
c1 = array([7., 8., 9.])
|
||||
- local_dict={'a': a1, 'b': b1, 'c': c1}
|
||||
- x = evaluate("2*a + 3*b*c", local_dict=local_dict)
|
||||
- x = re_evaluate(local_dict=local_dict)
|
||||
+ x = evaluate("2*a + 3*b*c", local_dict={'a': a1, 'b': b1, 'c': c1})
|
||||
+ x = re_evaluate()
|
||||
assert_array_equal(x, array([86., 124., 168.]))
|
||||
|
||||
def test_validate(self):
|
||||
@@ -401,10 +400,9 @@ class test_evaluate(TestCase):
|
||||
a1 = array([1., 2., 3.])
|
||||
b1 = array([4., 5., 6.])
|
||||
c1 = array([7., 8., 9.])
|
||||
- local_dict={'a': a1, 'b': b1, 'c': c1}
|
||||
- retval = validate("2*a + 3*b*c", local_dict=local_dict)
|
||||
+ retval = validate("2*a + 3*b*c", local_dict={'a': a1, 'b': b1, 'c': c1})
|
||||
assert(retval is None)
|
||||
- x = re_evaluate(local_dict=local_dict)
|
||||
+ x = re_evaluate()
|
||||
assert_array_equal(x, array([86., 124., 168.]))
|
||||
|
||||
# Test for issue #22
|
||||
@@ -504,49 +502,11 @@ class test_evaluate(TestCase):
|
||||
a = arange(3)
|
||||
try:
|
||||
evaluate("a < [0, 0, 0]")
|
||||
- except (ValueError, TypeError):
|
||||
+ except TypeError:
|
||||
pass
|
||||
else:
|
||||
self.fail()
|
||||
|
||||
- def test_forbidden_tokens(self):
|
||||
- # Forbid dunder
|
||||
- try:
|
||||
- evaluate('__builtins__')
|
||||
- except ValueError:
|
||||
- pass
|
||||
- else:
|
||||
- self.fail()
|
||||
-
|
||||
- # Forbid colon for lambda funcs
|
||||
- try:
|
||||
- evaluate('lambda x: x')
|
||||
- except ValueError:
|
||||
- pass
|
||||
- else:
|
||||
- self.fail()
|
||||
-
|
||||
- # Forbid indexing
|
||||
- try:
|
||||
- evaluate('locals()[]')
|
||||
- except ValueError:
|
||||
- pass
|
||||
- else:
|
||||
- self.fail()
|
||||
-
|
||||
- # Forbid semicolon
|
||||
- try:
|
||||
- evaluate('import os; os.cpu_count()')
|
||||
- except ValueError:
|
||||
- pass
|
||||
- else:
|
||||
- self.fail()
|
||||
-
|
||||
- # I struggle to come up with cases for our ban on `'` and `"`
|
||||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
def test_disassemble(self):
|
||||
assert_equal(disassemble(NumExpr(
|
||||
"where(m, a, -1)", [('m', bool), ('a', float)])),
|
@ -0,0 +1,80 @@
|
||||
From d3f418e5f469be66f8e974e5b53487bd8698dea4 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
|
||||
Date: Wed, 16 Aug 2023 17:23:41 +0200
|
||||
Subject: [PATCH 4/7] Revert "Adding tests for `validate` and noticed that
|
||||
`re_evaluate` tests using `local_dict` argument are flawed and do not
|
||||
actually work"
|
||||
|
||||
This reverts commit 74d597398ba3c379c196e469d40f516de110eaa5.
|
||||
---
|
||||
numexpr/__init__.py | 3 +--
|
||||
numexpr/tests/test_numexpr.py | 32 ++------------------------------
|
||||
2 files changed, 3 insertions(+), 32 deletions(-)
|
||||
|
||||
diff --git a/numexpr/__init__.py b/numexpr/__init__.py
|
||||
index 7946f8522d..9cabe69589 100644
|
||||
--- a/numexpr/__init__.py
|
||||
+++ b/numexpr/__init__.py
|
||||
@@ -31,8 +31,7 @@ is_cpu_amd_intel = False # DEPRECATION WARNING: WILL BE REMOVED IN FUTURE RELEAS
|
||||
import os, os.path
|
||||
import platform
|
||||
from numexpr.expressions import E
|
||||
-from numexpr.necompiler import (NumExpr, disassemble, evaluate, re_evaluate,
|
||||
- validate)
|
||||
+from numexpr.necompiler import NumExpr, disassemble, evaluate, re_evaluate
|
||||
|
||||
from numexpr.utils import (_init_num_threads,
|
||||
get_vml_version, set_vml_accuracy_mode, set_vml_num_threads,
|
||||
diff --git a/numexpr/tests/test_numexpr.py b/numexpr/tests/test_numexpr.py
|
||||
index ccb0b6cb07..32f5be4b75 100644
|
||||
--- a/numexpr/tests/test_numexpr.py
|
||||
+++ b/numexpr/tests/test_numexpr.py
|
||||
@@ -31,7 +31,7 @@ from numpy.testing import (assert_equal, assert_array_equal,
|
||||
from numpy import shape, allclose, array_equal, ravel, isnan, isinf
|
||||
|
||||
import numexpr
|
||||
-from numexpr import E, NumExpr, evaluate, re_evaluate, validate, disassemble, use_vml
|
||||
+from numexpr import E, NumExpr, evaluate, re_evaluate, disassemble, use_vml
|
||||
from numexpr.expressions import ConstantNode
|
||||
|
||||
import unittest
|
||||
@@ -370,38 +370,10 @@ class test_evaluate(TestCase):
|
||||
assert_array_equal(x, array([86., 124., 168.]))
|
||||
|
||||
def test_re_evaluate_dict(self):
|
||||
- a1 = array([1., 2., 3.])
|
||||
- b1 = array([4., 5., 6.])
|
||||
- c1 = array([7., 8., 9.])
|
||||
- x = evaluate("2*a + 3*b*c", local_dict={'a': a1, 'b': b1, 'c': c1})
|
||||
- x = re_evaluate()
|
||||
- assert_array_equal(x, array([86., 124., 168.]))
|
||||
-
|
||||
- def test_validate(self):
|
||||
a = array([1., 2., 3.])
|
||||
b = array([4., 5., 6.])
|
||||
c = array([7., 8., 9.])
|
||||
- retval = validate("2*a + 3*b*c")
|
||||
- assert(retval is None)
|
||||
- x = re_evaluate()
|
||||
- assert_array_equal(x, array([86., 124., 168.]))
|
||||
-
|
||||
- def test_validate_missing_var(self):
|
||||
- a = array([1., 2., 3.])
|
||||
- b = array([4., 5., 6.])
|
||||
- retval = validate("2*a + 3*b*c")
|
||||
- assert(isinstance(retval, KeyError))
|
||||
-
|
||||
- def test_validate_syntax(self):
|
||||
- retval = validate("2+")
|
||||
- assert(isinstance(retval, SyntaxError))
|
||||
-
|
||||
- def test_validate_dict(self):
|
||||
- a1 = array([1., 2., 3.])
|
||||
- b1 = array([4., 5., 6.])
|
||||
- c1 = array([7., 8., 9.])
|
||||
- retval = validate("2*a + 3*b*c", local_dict={'a': a1, 'b': b1, 'c': c1})
|
||||
- assert(retval is None)
|
||||
+ x = evaluate("2*a + 3*b*c", local_dict={'a': a, 'b': b, 'c': c})
|
||||
x = re_evaluate()
|
||||
assert_array_equal(x, array([86., 124., 168.]))
|
||||
|
@ -0,0 +1,26 @@
|
||||
From 5bdab6e1f4d896c251270fdbe6efe77120670500 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
|
||||
Date: Wed, 16 Aug 2023 17:24:33 +0200
|
||||
Subject: [PATCH 5/7] Revert "Add in docstring intro for `validate(...)`"
|
||||
|
||||
This reverts commit da39566c2c379888991e4eb1dae95d917e13db8a.
|
||||
---
|
||||
numexpr/necompiler.py | 5 +----
|
||||
1 file changed, 1 insertion(+), 4 deletions(-)
|
||||
|
||||
diff --git a/numexpr/necompiler.py b/numexpr/necompiler.py
|
||||
index 37052acadb..f846c1d23d 100644
|
||||
--- a/numexpr/necompiler.py
|
||||
+++ b/numexpr/necompiler.py
|
||||
@@ -772,10 +772,7 @@ def validate(ex: str,
|
||||
_frame_depth: int = 2,
|
||||
**kwargs) -> Optional[Exception]:
|
||||
"""
|
||||
- Validate a NumExpr expression with the given `local_dict` or `locals()`.
|
||||
- Returns `None` on success and the Exception object if one occurs. Note that
|
||||
- you can proceed directly to call `re_evaluate()` if you use `validate()`
|
||||
- to sanitize your expressions and variables in advance.
|
||||
+ Returns
|
||||
|
||||
Parameters
|
||||
----------
|
@ -0,0 +1,307 @@
|
||||
From b773b8ba12431b7d15f9812e29861de327d0cd8f Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
|
||||
Date: Wed, 16 Aug 2023 17:24:36 +0200
|
||||
Subject: [PATCH 6/7] Revert "Add a `validate(...)` function that can be used
|
||||
to sansitize the expression and variables before starting the virtual
|
||||
machine."
|
||||
|
||||
This reverts commit 21ff376a0853aff6aea63343c6010d6434917319.
|
||||
---
|
||||
numexpr/necompiler.py | 194 +++++++++++-------------------------------
|
||||
1 file changed, 51 insertions(+), 143 deletions(-)
|
||||
|
||||
diff --git a/numexpr/necompiler.py b/numexpr/necompiler.py
|
||||
index f846c1d23d..9291d44244 100644
|
||||
--- a/numexpr/necompiler.py
|
||||
+++ b/numexpr/necompiler.py
|
||||
@@ -8,7 +8,6 @@
|
||||
# rights to use.
|
||||
####################################################################
|
||||
|
||||
-from typing import Optional, Dict
|
||||
import __future__
|
||||
import sys
|
||||
import numpy
|
||||
@@ -527,7 +526,7 @@ context_info = [
|
||||
]
|
||||
|
||||
|
||||
-def getContext(kwargs, _frame_depth=1):
|
||||
+def getContext(kwargs, frame_depth=1):
|
||||
d = kwargs.copy()
|
||||
context = {}
|
||||
for name, allowed, default in context_info:
|
||||
@@ -536,11 +535,11 @@ def getContext(kwargs, _frame_depth=1):
|
||||
context[name] = value
|
||||
else:
|
||||
raise ValueError("'%s' must be one of %s" % (name, allowed))
|
||||
-
|
||||
+
|
||||
if d:
|
||||
raise ValueError("Unknown keyword argument '%s'" % d.popitem()[0])
|
||||
if context['truediv'] == 'auto':
|
||||
- caller_globals = sys._getframe(_frame_depth + 1).f_globals
|
||||
+ caller_globals = sys._getframe(frame_depth + 1).f_globals
|
||||
context['truediv'] = caller_globals.get('division', None) == __future__.division
|
||||
|
||||
return context
|
||||
@@ -614,11 +613,11 @@ def NumExpr(ex, signature=(), **kwargs):
|
||||
# NumExpr can be called either directly by the end-user, in which case
|
||||
# kwargs need to be sanitized by getContext, or by evaluate,
|
||||
# in which case kwargs are in already sanitized.
|
||||
- # In that case _frame_depth is wrong (it should be 2) but it doesn't matter
|
||||
+ # In that case frame_depth is wrong (it should be 2) but it doesn't matter
|
||||
# since it will not be used (because truediv='auto' has already been
|
||||
# translated to either True or False).
|
||||
- _frame_depth = 1
|
||||
- context = getContext(kwargs, _frame_depth=_frame_depth)
|
||||
+
|
||||
+ context = getContext(kwargs, frame_depth=1)
|
||||
threeAddrProgram, inputsig, tempsig, constants, input_names = precompile(ex, signature, context)
|
||||
program = compileThreeAddrForm(threeAddrProgram)
|
||||
return interpreter.NumExpr(inputsig.encode('ascii'),
|
||||
@@ -718,11 +717,11 @@ def getExprNames(text, context):
|
||||
return [a.value for a in input_order], ex_uses_vml
|
||||
|
||||
|
||||
-def getArguments(names, local_dict=None, global_dict=None, _frame_depth: int=2):
|
||||
+def getArguments(names, local_dict=None, global_dict=None):
|
||||
"""
|
||||
Get the arguments based on the names.
|
||||
"""
|
||||
- call_frame = sys._getframe(_frame_depth)
|
||||
+ call_frame = sys._getframe(2)
|
||||
|
||||
clear_local_dict = False
|
||||
if local_dict is None:
|
||||
@@ -761,40 +760,32 @@ _numexpr_last = {}
|
||||
|
||||
evaluate_lock = threading.Lock()
|
||||
|
||||
-# MAYBE: decorate this function to add attributes instead of having the
|
||||
-# _numexpr_last dictionary?
|
||||
-def validate(ex: str,
|
||||
- local_dict: Optional[Dict] = None,
|
||||
- global_dict: Optional[Dict] = None,
|
||||
- out: numpy.ndarray = None,
|
||||
- order: str = 'K',
|
||||
- casting: str = 'safe',
|
||||
- _frame_depth: int = 2,
|
||||
- **kwargs) -> Optional[Exception]:
|
||||
+def evaluate(ex, local_dict=None, global_dict=None,
|
||||
+ out=None, order='K', casting='safe', **kwargs):
|
||||
"""
|
||||
- Returns
|
||||
+ Evaluate a simple array expression element-wise, using the new iterator.
|
||||
+
|
||||
+ ex is a string forming an expression, like "2*a+3*b". The values for "a"
|
||||
+ and "b" will by default be taken from the calling function's frame
|
||||
+ (through use of sys._getframe()). Alternatively, they can be specifed
|
||||
+ using the 'local_dict' or 'global_dict' arguments.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
- ex: str
|
||||
- a string forming an expression, like "2*a+3*b". The values for "a"
|
||||
- and "b" will by default be taken from the calling function's frame
|
||||
- (through use of sys._getframe()). Alternatively, they can be specified
|
||||
- using the 'local_dict' or 'global_dict' arguments.
|
||||
|
||||
- local_dict: dictionary, optional
|
||||
+ local_dict : dictionary, optional
|
||||
A dictionary that replaces the local operands in current frame.
|
||||
|
||||
- global_dict: dictionary, optional
|
||||
+ global_dict : dictionary, optional
|
||||
A dictionary that replaces the global operands in current frame.
|
||||
|
||||
- out: NumPy array, optional
|
||||
+ out : NumPy array, optional
|
||||
An existing array where the outcome is going to be stored. Care is
|
||||
required so that this array has the same shape and type than the
|
||||
actual outcome of the computation. Useful for avoiding unnecessary
|
||||
new array allocations.
|
||||
|
||||
- order: {'C', 'F', 'A', or 'K'}, optional
|
||||
+ order : {'C', 'F', 'A', or 'K'}, optional
|
||||
Controls the iteration order for operands. 'C' means C order, 'F'
|
||||
means Fortran order, 'A' means 'F' order if all the arrays are
|
||||
Fortran contiguous, 'C' order otherwise, and 'K' means as close to
|
||||
@@ -802,7 +793,7 @@ def validate(ex: str,
|
||||
efficient computations, typically 'K'eep order (the default) is
|
||||
desired.
|
||||
|
||||
- casting: {'no', 'equiv', 'safe', 'same_kind', 'unsafe'}, optional
|
||||
+ casting : {'no', 'equiv', 'safe', 'same_kind', 'unsafe'}, optional
|
||||
Controls what kind of data casting may occur when making a copy or
|
||||
buffering. Setting this to 'unsafe' is not recommended, as it can
|
||||
adversely affect accumulations.
|
||||
@@ -813,117 +804,37 @@ def validate(ex: str,
|
||||
* 'same_kind' means only safe casts or casts within a kind,
|
||||
like float64 to float32, are allowed.
|
||||
* 'unsafe' means any data conversions may be done.
|
||||
-
|
||||
- _frame_depth: int
|
||||
- The calling frame depth. Unless you are a NumExpr developer you should
|
||||
- not set this value.
|
||||
"""
|
||||
global _numexpr_last
|
||||
-
|
||||
- try:
|
||||
-
|
||||
- if not isinstance(ex, str):
|
||||
- raise ValueError("must specify expression as a string")
|
||||
-
|
||||
- # Get the names for this expression
|
||||
- context = getContext(kwargs)
|
||||
- expr_key = (ex, tuple(sorted(context.items())))
|
||||
- if expr_key not in _names_cache:
|
||||
- _names_cache[expr_key] = getExprNames(ex, context)
|
||||
- names, ex_uses_vml = _names_cache[expr_key]
|
||||
- arguments = getArguments(names, local_dict, global_dict, _frame_depth=_frame_depth)
|
||||
-
|
||||
- # Create a signature
|
||||
- signature = [(name, getType(arg)) for (name, arg) in
|
||||
- zip(names, arguments)]
|
||||
-
|
||||
- # Look up numexpr if possible.
|
||||
- numexpr_key = expr_key + (tuple(signature),)
|
||||
- try:
|
||||
- compiled_ex = _numexpr_cache[numexpr_key]
|
||||
- except KeyError:
|
||||
- compiled_ex = _numexpr_cache[numexpr_key] = NumExpr(ex, signature, **context)
|
||||
- kwargs = {'out': out, 'order': order, 'casting': casting,
|
||||
- 'ex_uses_vml': ex_uses_vml}
|
||||
- _numexpr_last = dict(ex=compiled_ex, argnames=names, kwargs=kwargs)
|
||||
- # with evaluate_lock:
|
||||
- # return compiled_ex(*arguments, **kwargs)
|
||||
- except Exception as e:
|
||||
- return e
|
||||
- return None
|
||||
-
|
||||
-def evaluate(ex: str,
|
||||
- local_dict: Optional[Dict] = None,
|
||||
- global_dict: Optional[Dict] = None,
|
||||
- out: numpy.ndarray = None,
|
||||
- order: str = 'K',
|
||||
- casting: str = 'safe',
|
||||
- _frame_depth: int=3,
|
||||
- **kwargs) -> numpy.ndarray:
|
||||
- """
|
||||
- Evaluate a simple array expression element-wise using the virtual machine.
|
||||
-
|
||||
- Parameters
|
||||
- ----------
|
||||
- ex: str
|
||||
- a string forming an expression, like "2*a+3*b". The values for "a"
|
||||
- and "b" will by default be taken from the calling function's frame
|
||||
- (through use of sys._getframe()). Alternatively, they can be specified
|
||||
- using the 'local_dict' or 'global_dict' arguments.
|
||||
-
|
||||
- local_dict: dictionary, optional
|
||||
- A dictionary that replaces the local operands in current frame.
|
||||
-
|
||||
- global_dict: dictionary, optional
|
||||
- A dictionary that replaces the global operands in current frame.
|
||||
-
|
||||
- out: NumPy array, optional
|
||||
- An existing array where the outcome is going to be stored. Care is
|
||||
- required so that this array has the same shape and type than the
|
||||
- actual outcome of the computation. Useful for avoiding unnecessary
|
||||
- new array allocations.
|
||||
-
|
||||
- order: {'C', 'F', 'A', or 'K'}, optional
|
||||
- Controls the iteration order for operands. 'C' means C order, 'F'
|
||||
- means Fortran order, 'A' means 'F' order if all the arrays are
|
||||
- Fortran contiguous, 'C' order otherwise, and 'K' means as close to
|
||||
- the order the array elements appear in memory as possible. For
|
||||
- efficient computations, typically 'K'eep order (the default) is
|
||||
- desired.
|
||||
-
|
||||
- casting: {'no', 'equiv', 'safe', 'same_kind', 'unsafe'}, optional
|
||||
- Controls what kind of data casting may occur when making a copy or
|
||||
- buffering. Setting this to 'unsafe' is not recommended, as it can
|
||||
- adversely affect accumulations.
|
||||
-
|
||||
- * 'no' means the data types should not be cast at all.
|
||||
- * 'equiv' means only byte-order changes are allowed.
|
||||
- * 'safe' means only casts which can preserve values are allowed.
|
||||
- * 'same_kind' means only safe casts or casts within a kind,
|
||||
- like float64 to float32, are allowed.
|
||||
- * 'unsafe' means any data conversions may be done.
|
||||
-
|
||||
- _frame_depth: int
|
||||
- The calling frame depth. Unless you are a NumExpr developer you should
|
||||
- not set this value.
|
||||
- """
|
||||
- # We could avoid code duplication if we called validate and then re_evaluate
|
||||
- # here, but they we have difficulties with the `sys.getframe(2)` call in
|
||||
- # `getArguments`
|
||||
- e = validate(ex, local_dict=local_dict, global_dict=global_dict,
|
||||
- out=out, order=order, casting=casting,
|
||||
- _frame_depth=_frame_depth, **kwargs)
|
||||
- if e is None:
|
||||
- return re_evaluate(local_dict=local_dict, _frame_depth=_frame_depth)
|
||||
- else:
|
||||
- raise e
|
||||
+ if not isinstance(ex, str):
|
||||
+ raise ValueError("must specify expression as a string")
|
||||
|
||||
+ # Get the names for this expression
|
||||
+ context = getContext(kwargs, frame_depth=1)
|
||||
+ expr_key = (ex, tuple(sorted(context.items())))
|
||||
+ if expr_key not in _names_cache:
|
||||
+ _names_cache[expr_key] = getExprNames(ex, context)
|
||||
+ names, ex_uses_vml = _names_cache[expr_key]
|
||||
+ arguments = getArguments(names, local_dict, global_dict)
|
||||
|
||||
-
|
||||
+ # Create a signature
|
||||
+ signature = [(name, getType(arg)) for (name, arg) in
|
||||
+ zip(names, arguments)]
|
||||
|
||||
+ # Look up numexpr if possible.
|
||||
+ numexpr_key = expr_key + (tuple(signature),)
|
||||
+ try:
|
||||
+ compiled_ex = _numexpr_cache[numexpr_key]
|
||||
+ except KeyError:
|
||||
+ compiled_ex = _numexpr_cache[numexpr_key] = NumExpr(ex, signature, **context)
|
||||
+ kwargs = {'out': out, 'order': order, 'casting': casting,
|
||||
+ 'ex_uses_vml': ex_uses_vml}
|
||||
+ _numexpr_last = dict(ex=compiled_ex, argnames=names, kwargs=kwargs)
|
||||
+ with evaluate_lock:
|
||||
+ return compiled_ex(*arguments, **kwargs)
|
||||
|
||||
-def re_evaluate(local_dict: Optional[Dict] = None,
|
||||
- _frame_depth: int=2) -> numpy.ndarray:
|
||||
+
|
||||
+def re_evaluate(local_dict=None):
|
||||
"""
|
||||
Re-evaluate the previous executed array expression without any check.
|
||||
|
||||
@@ -933,20 +844,17 @@ def re_evaluate(local_dict: Optional[Dict] = None,
|
||||
|
||||
Parameters
|
||||
----------
|
||||
- local_dict: dictionary, optional
|
||||
+
|
||||
+ local_dict : dictionary, optional
|
||||
A dictionary that replaces the local operands in current frame.
|
||||
- _frame_depth: int
|
||||
- The calling frame depth. Unless you are a NumExpr developer you should
|
||||
- not set this value.
|
||||
+
|
||||
"""
|
||||
- global _numexpr_last
|
||||
-
|
||||
try:
|
||||
compiled_ex = _numexpr_last['ex']
|
||||
except KeyError:
|
||||
- raise RuntimeError("A previous evaluate() execution was not found, please call `validate` or `evaluate` once before `re_evaluate`")
|
||||
+ raise RuntimeError("not a previous evaluate() execution found")
|
||||
argnames = _numexpr_last['argnames']
|
||||
- args = getArguments(argnames, local_dict, _frame_depth=_frame_depth)
|
||||
+ args = getArguments(argnames, local_dict)
|
||||
kwargs = _numexpr_last['kwargs']
|
||||
with evaluate_lock:
|
||||
return compiled_ex(*args, **kwargs)
|
@ -0,0 +1,31 @@
|
||||
From 89696f397f167958a6dfc8ccb1f0d796fb4307a3 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
|
||||
Date: Wed, 16 Aug 2023 17:11:28 +0200
|
||||
Subject: [PATCH 7/7] Use r"" to avoid warning about unknown escapes
|
||||
|
||||
---
|
||||
numexpr/cpuinfo.py | 4 ++--
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/numexpr/cpuinfo.py b/numexpr/cpuinfo.py
|
||||
index 4f934b7ba2..4a57d3cb03 100755
|
||||
--- a/numexpr/cpuinfo.py
|
||||
+++ b/numexpr/cpuinfo.py
|
||||
@@ -105,7 +105,7 @@ class CPUInfoBase(object):
|
||||
|
||||
def __get_nbits(self):
|
||||
abits = platform.architecture()[0]
|
||||
- nbits = re.compile('(\d+)bit').search(abits).group(1)
|
||||
+ nbits = re.compile(r'(\d+)bit').search(abits).group(1)
|
||||
return nbits
|
||||
|
||||
def _is_32bit(self):
|
||||
@@ -658,7 +658,7 @@ class Win32CPUInfo(CPUInfoBase):
|
||||
#XXX: Bad style to use so long `try:...except:...`. Fix it!
|
||||
|
||||
prgx = re.compile(r"family\s+(?P<FML>\d+)\s+model\s+(?P<MDL>\d+)"
|
||||
- "\s+stepping\s+(?P<STP>\d+)", re.IGNORECASE)
|
||||
+ r"\s+stepping\s+(?P<STP>\d+)", re.IGNORECASE)
|
||||
chnd = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, self.pkey)
|
||||
pnum = 0
|
||||
while 1:
|
@ -0,0 +1,28 @@
|
||||
From fcceab4c0c59bdbcf79ebca844ac3865bb6e795d Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= <miro@hroncok.cz>
|
||||
Date: Thu, 30 May 2024 17:04:50 +0200
|
||||
Subject: [PATCH] Fix necompiler.getArguments() on Python 3.13.0b1
|
||||
|
||||
The FrameLocalsProxy object (PEP 667) cannot be cleared.
|
||||
|
||||
Fixes https://github.com/pydata/numexpr/issues/488
|
||||
---
|
||||
numexpr/necompiler.py | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/numexpr/necompiler.py b/numexpr/necompiler.py
|
||||
index 5126bd7..1e60b5a 100644
|
||||
--- a/numexpr/necompiler.py
|
||||
+++ b/numexpr/necompiler.py
|
||||
@@ -767,7 +767,7 @@ def getArguments(names, local_dict=None, global_dict=None, _frame_depth: int=2):
|
||||
# If we generated local_dict via an explicit reference to f_locals,
|
||||
# clear the dict to prevent creating extra ref counts in the caller's scope
|
||||
# See https://github.com/pydata/numexpr/issues/310
|
||||
- if clear_local_dict:
|
||||
+ if clear_local_dict and hasattr(local_dict, 'clear'):
|
||||
local_dict.clear()
|
||||
|
||||
return arguments
|
||||
--
|
||||
2.45.0
|
||||
|
Loading…
Reference in new issue