8000 Merge pull request #23936 from seberg/errstate-contextvar · numpy/numpy@8d1de4c · GitHub
[go: up one dir, main page]

Skip to content

Commit 8d1de4c

Browse files
authored
Merge pull request #23936 from seberg/errstate-contextvar
ENH,API: Make the errstate/extobj a contextvar
2 parents f4bf16c + a2ce769 commit 8d1de4c

File tree

17 files changed

+490
-479
lines changed

17 files changed

+490
-479
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
* Being fully context and thread-safe, ``np.errstate`` can only
2+
be entered once now. It is possible to enter it again after
3+
exiting first.
4+
* ``np.setbufsize`` is now tied to ``np.errstate()``: Leaving an
5+
``np.errstate`` context will also reset the ``bufsize``.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
``np.errstate()`` is now faster and context safe
2+
------------------------------------------------
3+
The `np.errstate` context manager/decorator is now faster and
4+
safer. Previously, it was not context safe and had (rarely)
5+
issues with thread-safety.

doc/source/reference/c-api/ufunc.rst

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,7 @@ UFunc API
1010
Constants
1111
---------
1212

13-
``UFUNC_ERR_{HANDLER}``
14-
.. c:macro:: UFUNC_ERR_IGNORE
15-
16-
.. c:macro:: UFUNC_ERR_WARN
17-
18-
.. c:macro:: UFUNC_ERR_RAISE
19-
20-
.. c:macro:: UFUNC_ERR_CALL
21-
2213
``UFUNC_{THING}_{ERR}``
23-
.. c:macro:: UFUNC_MASK_DIVIDEBYZERO
24-
25-
.. c:macro:: UFUNC_MASK_OVERFLOW
26-
27-
.. c:macro:: UFUNC_MASK_UNDERFLOW
28-
29-
.. c:macro:: UFUNC_MASK_INVALID
30-
31-
.. c:macro:: UFUNC_SHIFT_DIVIDEBYZERO
32-
33-
.. c:macro:: UFUNC_SHIFT_OVERFLOW
34-
35-
.. c:macro:: UFUNC_SHIFT_UNDERFLOW
36-
37-
.. c:macro:: UFUNC_SHIFT_INVALID
38-
3914
.. c:macro:: UFUNC_FPE_DIVIDEBYZERO
4015
4116
.. c:macro:: UFUNC_FPE_OVERFLOW

numpy/__init__.cython-30.pxd

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -875,26 +875,10 @@ cdef extern from "numpy/ufuncobject.h":
875875
PyUFunc_Zero
876876
PyUFunc_One
877877
PyUFunc_None
878-
UFUNC_ERR_IGNORE
879-
UFUNC_ERR_WARN
880-
UFUNC_ERR_RAISE
881-
UFUNC_ERR_CALL
882-
UFUNC_ERR_PRINT
883-
UFUNC_ERR_LOG
884-
UFUNC_MASK_DIVIDEBYZERO
885-
UFUNC_MASK_OVERFLOW
886-
UFUNC_MASK_UNDERFLOW
887-
UFUNC_MASK_INVALID
888-
UFUNC_SHIFT_DIVIDEBYZERO
889-
UFUNC_SHIFT_OVERFLOW
890-
UFUNC_SHIFT_UNDERFLOW
891-
UFUNC_SHIFT_INVALID
892878
UFUNC_FPE_DIVIDEBYZERO
893879
UFUNC_FPE_OVERFLOW
894880
UFUNC_FPE_UNDERFLOW
895881
UFUNC_FPE_INVALID
896-
UFUNC_ERR_DEFAULT
897-
UFUNC_ERR_DEFAULT2
898882

899883
object PyUFunc_FromFuncAndData(PyUFuncGenericFunction *,
900884
void **, char *, int, int, int, int, char *, char *, int)

numpy/__init__.pxd

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -833,26 +833,10 @@ cdef extern from "numpy/ufuncobject.h":
833833
PyUFunc_Zero
834834
PyUFunc_One
835835
PyUFunc_None
836-
UFUNC_ERR_IGNORE
837-
UFUNC_ERR_WARN
838-
UFUNC_ERR_RAISE
839-
UFUNC_ERR_CALL
840-
UFUNC_ERR_PRINT
841-
UFUNC_ERR_LOG
842-
UFUNC_MASK_DIVIDEBYZERO
843-
UFUNC_MASK_OVERFLOW
844-
UFUNC_MASK_UNDERFLOW
845-
UFUNC_MASK_INVALID
846-
UFUNC_SHIFT_DIVIDEBYZERO
847-
UFUNC_SHIFT_OVERFLOW
848-
UFUNC_SHIFT_UNDERFLOW
849-
UFUNC_SHIFT_INVALID
850836
UFUNC_FPE_DIVIDEBYZERO
851837
UFUNC_FPE_OVERFLOW
852838
UFUNC_FPE_UNDERFLOW
853839
UFUNC_FPE_INVALID
854-
UFUNC_ERR_DEFAULT
855-
UFUNC_ERR_DEFAULT2
856840

857841
object PyUFunc_FromFuncAndData(PyUFuncGenericFunction *,
858842
void **, char *, int, int, int, int, char *, char *, int)

numpy/__init__.pyi

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3154,19 +3154,6 @@ infty: Final[float]
31543154
nan: Final[float]
31553155
pi: Final[float]
31563156

3157-
ERR_IGNORE: L[0]
3158-
ERR_WARN: L[1]
3159-
ERR_RAISE: L[2]
3160-
ERR_CALL: L[3]
3161-
ERR_PRINT: L[4]
3162-
ERR_LOG: L[5]
3163-
ERR_DEFAULT: L[521]
3164-
3165-
SHIFT_DIVIDEBYZERO: L[0]
3166-
SHIFT_OVERFLOW: L[3]
3167-
SHIFT_UNDERFLOW: L[6]
3168-
SHIFT_INVALID: L[9]
3169-
31703157
FPE_DIVIDEBYZERO: L[1]
31713158
FPE_OVERFLOW: L[2]
31723159
FPE_UNDERFLOW: L[4]

numpy/core/_ufunc_config.py

Lines changed: 55 additions & 86 deletions
< 7802 td data-grid-cell-id="diff-a548d1ab35ebcee7cc3f1ba53e24d9df93e97b03ba41cc7005cb0f49b39daab0-24-18-0" data-selected="false" role="gridcell" style="background-color:var(--diffBlob-deletionNum-bgColor, var(--diffBlob-deletion-bgColor-num));text-align:center" tabindex="-1" valign="top" class="focusable-grid-cell diff-line-number position-relative left-side">24
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,21 @@
11
"""
22
Functions for changing global ufunc configuration
33
4-
This provides helpers which wrap `umath._geterrobj` and `umath._seterrobj`
4+
This provides helpers which wrap `_get_extobj_dict` and `_make_extobj`, and
5+
`_extobj_contextvar` from umath.
56
"""
67
import collections.abc
78
import contextlib
89
import contextvars
910

1011
from .._utils import set_module
11-
from .umath import (
12-
UFUNC_BUFSIZE_DEFAULT,
13-
ERR_IGNORE, ERR_WARN, ERR_RAISE, ERR_CALL, ERR_PRINT, ERR_LOG, ERR_DEFAULT,
14-
SHIFT_DIVIDEBYZERO, SHIFT_OVERFLOW, SHIFT_UNDERFLOW, SHIFT_INVALID,
15-
)
16-
from . import umath
12+
from .umath import _make_extobj, _get_extobj_dict, _extobj_contextvar
1713

1814
__all__ = [
1915
"seterr", "geterr", "setbufsize", "getbufsize", "seterrcall", "geterrcall",
2016
"errstate", '_no_nep50_warning'
2117
]
2218

23-
_errdict = {"ignore": ERR_IGNORE,
-
"warn": ERR_WARN,
25-
"raise": ERR_RAISE,
26-
"call": ERR_CALL,
27-
"print": ERR_PRINT,
28-
"log": ERR_LOG}
29-
30-
_errdict_rev = {value: key for key, value in _errdict.items()}
31-
3219

3320
@set_module('numpy')
3421
def seterr(all=None, divide=None, over=None, under=None, invalid=None):
@@ -106,25 +93,14 @@ def seterr(all=None, divide=None, over=None, under=None, invalid=None):
10693
10794
"""
10895

109-
pyvals = umath._geterrobj()
110-
old = geterr()
111-
112-
if divide is None:
113-
divide = all or old['divide']
114-
if over is None:
115-
over = all or old['over']
116-
if under is None:
117-
under = all or old['under']
118-
if invalid is None:
119-
invalid = all or old['invalid']
120-
121-
maskvalue = ((_errdict[divide] << SHIFT_DIVIDEBYZERO) +
122-
(_errdict[over] << SHIFT_OVERFLOW) +
123-
(_errdict[under] << SHIFT_UNDERFLOW) +
124-
(_errdict[invalid] << SHIFT_INVALID))
125-
126-
pyvals[1] = maskvalue
127-
umath._seterrobj(pyvals)
96+
old = _get_extobj_dict()
97+
# The errstate doesn't include call and bufsize, so pop them:
98+
old.pop("call", None)
99+
old.pop("bufsize", None)
100+
101+
extobj = _make_extobj(
102+
all=all, divide=divide, over=over, under=under, invalid=invalid)
103+
_extobj_contextvar.set(extobj)
128104
return old
129105

130106

@@ -168,17 +144,10 @@ def geterr():
168144
>>> oldsettings = np.seterr(**oldsettings) # restore original
169145
170146
"""
171-
maskvalue = umath._geterrobj()[1]
172-
mask = 7
173-
res = {}
174-
val = (maskvalue >> SHIFT_DIVIDEBYZERO) & mask
175-
res['divide'] = _errdict_rev[val]
176-
val = (maskvalue >> SHIFT_OVERFLOW) & mask
177-
res['over'] = _errdict_rev[val]
178-
val = (maskvalue >> SHIFT_UNDERFLOW) & mask
179-
res['under'] = _errdict_rev[val]
180-
val = (maskvalue >> SHIFT_INVALID) & mask
181-
res['invalid'] = _errdict_rev[val]
147+
res = _get_extobj_dict()
148+
# The "geterr" doesn't include call and bufsize,:
149+
res.pop("call", None)
150+
res.pop("bufsize", None)
182151
return res
183152

184153

@@ -187,23 +156,19 @@ def setbufsize(size):
187156
"""
188157
Set the size of the buffer used in ufuncs.
189158
159+
.. versionchanged:: 2.0
160+
The scope of setting the buffer is tied to the `np.errstate` context.
161+
Exiting a ``with errstate():` will also restore the bufsize.
162+
190163
Parameters
191164
----------
192165
size : int
193166
Size of buffer.
194167
195168
"""
196-
if size > 10e6:
197-
raise ValueError("Buffer size, %s, is too big." % size)
198-
if size < 5:
199-
raise ValueError("Buffer size, %s, is too small." % size)
200-
if size % 16 != 0:
201-
raise ValueError("Buffer size, %s, is not a multiple of 16." % size)
202 10000 -
203-
pyvals = umath._geterrobj()
204-
old = getbufsize()
205-
pyvals[0] = size
206-
umath._seterrobj(pyvals)
169+
old = _get_extobj_dict()["bufsize"]
170+
extobj = _make_extobj(bufsize=size)
171+
_extobj_contextvar.set(extobj)
207172
return old
208173

209174

@@ -218,7 +183,7 @@ def getbufsize():
218183
Size of ufunc buffer in bytes.
219184
220185
"""
221-
return umath._geterrobj()[0]
186+
return _get_extobj_dict()["bufsize"]
222187

223188

224189
@set_module('numpy')
@@ -303,14 +268,9 @@ def seterrcall(func):
303268
{'divide': 'log', 'over': 'log', 'under': 'log', 'invalid': 'log'}
304269
305270
"""
306-
if func is not None and not isinstance(func, collections.abc.Callable):
307-
if (not hasattr(func, 'write') or
308-
not isinstance(func.write, collections.abc.Callable)):
309-
raise ValueError("Only callable can be used as callback")
310-
pyvals = umath._geterrobj()
311-
old = geterrcall()
312-
pyvals[2] = func
313-
umath._seterrobj(pyvals)
271+
old = _get_extobj_dict()["call"]
272+
extobj = _make_extobj(call=func)
273+
_extobj_contextvar.set(extobj)
314274
return old
315275

316276

@@ -359,7 +319,7 @@ def geterrcall():
359319
>>> old_handler = np.seterrcall(None) # restore original
360320
361321
"""
362-
return umath._geterrobj()[2]
322+
return _get_extobj_dict()["call"]
363323

364324

365325
class _unspecified:
@@ -428,29 +388,38 @@ class errstate(contextlib.ContextDecorator):
428388
>>> olderr = np.seterr(**olderr) # restore original state
429389
430390
"""
431-
432-
def __init__(self, *, call=_Unspecified, **kwargs):
433-
self.call = call
434-
self.kwargs = kwargs
391+
__slots__ = [
392+
"_call", "_all", "_divide", "_over", "_under", "_invalid", "_token"]
393+
394+
def __init__(self, *, call=_Unspecified,
395+
all=None, divide=None, over=None, under=None, invalid=None):
396+
self._token = None
397+
self._call = call
398+
self._all = all
399+
self._divide = divide
400+
self._over = over
401+
self._under = under
402+
self._invalid = invalid
435403

436404
def __enter__(self):
437-
self.oldstate = seterr(**self.kwargs)
438-
if self.call is not _Unspecified:
439-
self.oldcall = seterrcall(self.call)
405+
if self._token is not None:
406+
raise TypeError("Cannot enter `np.errstate` twice.")
407+
if self._call is _Unspecified:
408+
extobj = _make_extobj(
409+
all=self._all, divide=self._divide, over=self._over,
410+
under=self._under, invalid=self._invalid)
411+
else:
412+
extobj = _make_extobj(
413+
call=self._call,
414+
all=self._all, divide=self._divide, over=self._over,
415+
under=self._under, invalid=self._invalid)
416+
417+
self._token = _extobj_contextvar.set(extobj)
440418

441419
def __exit__(self, *exc_info):
442-
seterr(**self.oldstate)
443-
if self.call is not _Unspecified:
444-
seterrcall(self.oldcall)
445-
446-
447-
def _setdef():
448-
defval = [UFUNC_BUFSIZE_DEFAULT, ERR_DEFAULT, None]
449-
umath._seterrobj(defval)
450-
451-
452-
# set the default values
453-
_setdef()
420+
_extobj_contextvar.reset(self._token)
421+
# Allow entering twice, so long as it is sequential:
422+
self._token = None
454423

455424

456425
NO_NEP50_WARNING = contextvars.ContextVar("_no_nep50_warning", default=False)

numpy/core/include/numpy/ufuncobject.h

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -231,34 +231,10 @@ typedef struct _tagPyUFuncObject {
231231
/* flags inferred during execution */
232232
#define UFUNC_CORE_DIM_MISSING 0x00040000
233233

234-
#define UFUNC_ERR_IGNORE 0
235-
#define UFUNC_ERR_WARN 1
236-
#define UFUNC_ERR_RAISE 2
237-
#define UFUNC_ERR_CALL 3
238-
#define UFUNC_ERR_PRINT 4
239-
#define UFUNC_ERR_LOG 5
240-
241-
/* Python side integer mask */
242-
243-
#define UFUNC_MASK_DIVIDEBYZERO 0x07
244-
#define UFUNC_MASK_OVERFLOW 0x3f
245-
#define UFUNC_MASK_UNDERFLOW 0x1ff
246-
#define UFUNC_MASK_INVALID 0xfff
247-
248-
#define UFUNC_SHIFT_DIVIDEBYZERO 0
249-
#define UFUNC_SHIFT_OVERFLOW 3
250-
#define UFUNC_SHIFT_UNDERFLOW 6
251-
#define UFUNC_SHIFT_INVALID 9
252-
253234

254235
#define UFUNC_OBJ_ISOBJECT 1
255236
#define UFUNC_OBJ_NEEDS_API 2
256237

257-
/* Default user error mode */
258-
#define UFUNC_ERR_DEFAULT \
259-
(UFUNC_ERR_WARN << UFUNC_SHIFT_DIVIDEBYZERO) + \
260-
(UFUNC_ERR_WARN << UFUNC_SHIFT_OVERFLOW) + \
261-
(UFUNC_ERR_WARN << UFUNC_SHIFT_INVALID)
262238

263239
#if NPY_ALLOW_THREADS
264240
#define NPY_LOOP_BEGIN_THREADS do {if (!(loop->obj & UFUNC_OBJ_NEEDS_API)) _save = PyEval_SaveThread();} while (0);

numpy/core/src/common/umathmodule.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
#include "ufunc_object.h"
55
#include "ufunc_type_resolution.h"
6+
#include "extobj.h" /* for the python side extobj set/get */
7+
68

79
NPY_NO_EXPORT PyObject *
810
get_sfloat_dtype(PyObject *NPY_UNUSED(mod), PyObject *NPY_UNUSED(args));

0 commit comments

Comments
 (0)
0