diff options
-rw-r--r-- | doc/neps/index.rst.tmpl | 10 | ||||
-rw-r--r-- | doc/neps/nep-0000.rst | 10 | ||||
-rw-r--r-- | doc/neps/nep-0016-abstract-array.rst | 328 | ||||
-rw-r--r-- | doc/neps/nep-0016-benchmark.py | 48 | ||||
-rw-r--r-- | doc/neps/nep-0022-ndarray-duck-typing-overview.rst | 3 | ||||
-rw-r--r-- | doc/neps/nep-0027-zero-rank-arrarys.rst | 251 | ||||
-rw-r--r-- | doc/release/1.16.0-notes.rst | 10 | ||||
-rw-r--r-- | numpy/core/include/numpy/ndarrayobject.h | 8 | ||||
-rw-r--r-- | numpy/core/include/numpy/npy_3kcompat.h | 20 | ||||
-rw-r--r-- | numpy/core/src/common/npy_longdouble.c | 18 | ||||
-rw-r--r-- | numpy/lib/tests/test_histograms.py | 7 | ||||
-rw-r--r-- | numpy/lib/tests/test_utils.py | 32 |
12 files changed, 711 insertions, 34 deletions
diff --git a/doc/neps/index.rst.tmpl b/doc/neps/index.rst.tmpl index e7b8fedba..bf4df3dfb 100644 --- a/doc/neps/index.rst.tmpl +++ b/doc/neps/index.rst.tmpl @@ -81,3 +81,13 @@ Rejected NEPs {% for nep, tags in neps.items() if tags['Status'] == 'Rejected' %} {{ tags['Title'] }} <{{ tags['Filename'] }}> {% endfor %} + +Withdrawn NEPs +-------------- + +.. toctree:: + :maxdepth: 1 + +{% for nep, tags in neps.items() if tags['Status'] == 'Withdrawn' %} + {{ tags['Title'] }} <{{ tags['Filename'] }}> +{% endfor %} diff --git a/doc/neps/nep-0000.rst b/doc/neps/nep-0000.rst index a3ec3a42b..5e719b0f9 100644 --- a/doc/neps/nep-0000.rst +++ b/doc/neps/nep-0000.rst @@ -31,12 +31,18 @@ feature proposal [1]_. Types ^^^^^ -There are two kinds of NEP: +There are three kinds of NEPs: 1. A **Standards Track** NEP describes a new feature or implementation for NumPy. -2. A **Process** NEP describes a process surrounding NumPy, or +2. An **Informational** NEP describes a NumPy design issue, or provides + general guidelines or information to the Python community, but does not + propose a new feature. Informational NEPs do not necessarily represent a + NumPy community consensus or recommendation, so users and implementers are + free to ignore Informational NEPs or follow their advice. + +3. A **Process** NEP describes a process surrounding NumPy, or proposes a change to (or an event in) a process. Process NEPs are like Standards Track NEPs but apply to areas other than the NumPy language itself. They may propose an implementation, but not to diff --git a/doc/neps/nep-0016-abstract-array.rst b/doc/neps/nep-0016-abstract-array.rst new file mode 100644 index 000000000..0dc201541 --- /dev/null +++ b/doc/neps/nep-0016-abstract-array.rst @@ -0,0 +1,328 @@ +============================================================= +NEP 16 — An abstract base class for identifying "duck arrays" +============================================================= + +:Author: Nathaniel J. Smith <njs@pobox.com> +:Status: Withdrawn +:Type: Standards Track +:Created: 2018-03-06 +:Resolution: https://github.com/numpy/numpy/pull/12174 + +.. note:: + + This NEP has been withdrawn in favor of the protocol based approach + described in + `NEP 22 <http://www.numpy.org/neps/nep-0022-ndarray-duck-typing-overview.html>`__ + +Abstract +-------- + +We propose to add an abstract base class ``AbstractArray`` so that +third-party classes can declare their ability to "quack like" an +``ndarray``, and an ``asabstractarray`` function that performs +similarly to ``asarray`` except that it passes through +``AbstractArray`` instances unchanged. + + +Detailed description +-------------------- + +Many functions, in NumPy and in third-party packages, start with some +code like:: + + def myfunc(a, b): + a = np.asarray(a) + b = np.asarray(b) + ... + +This ensures that ``a`` and ``b`` are ``np.ndarray`` objects, so +``myfunc`` can carry on assuming that they'll act like ndarrays both +semantically (at the Python level), and also in terms of how they're +stored in memory (at the C level). But many of these functions only +work with arrays at the Python level, which means that they don't +actually need ``ndarray`` objects *per se*: they could work just as +well with any Python object that "quacks like" an ndarray, such as +sparse arrays, dask's lazy arrays, or xarray's labeled arrays. + +However, currently, there's no way for these libraries to express that +their objects can quack like an ndarray, and there's no way for +functions like ``myfunc`` to express that they'd be happy with +anything that quacks like an ndarray. The purpose of this NEP is to +provide those two features. + +Sometimes people suggest using ``np.asanyarray`` for this purpose, but +unfortunately its semantics are exactly backwards: it guarantees that +the object it returns uses the same memory layout as an ``ndarray``, +but tells you nothing at all about its semantics, which makes it +essentially impossible to use safely in practice. Indeed, the two +``ndarray`` subclasses distributed with NumPy – ``np.matrix`` and +``np.ma.masked_array`` – do have incompatible semantics, and if they +were passed to a function like ``myfunc`` that doesn't check for them +as a special-case, then it may silently return incorrect results. + + +Declaring that an object can quack like an array +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +There are two basic approaches we could use for checking whether an +object quacks like an array. We could check for a special attribute on +the class:: + + def quacks_like_array(obj): + return bool(getattr(type(obj), "__quacks_like_array__", False)) + +Or, we could define an `abstract base class (ABC) +<https://docs.python.org/3/library/collections.abc.html>`__:: + + def quacks_like_array(obj): + return isinstance(obj, AbstractArray) + +If you look at how ABCs work, this is essentially equivalent to +keeping a global set of types that have been declared to implement the +``AbstractArray`` interface, and then checking it for membership. + +Between these, the ABC approach seems to have a number of advantages: + +* It's Python's standard, "one obvious way" of doing this. + +* ABCs can be introspected (e.g. ``help(np.AbstractArray)`` does + something useful). + +* ABCs can provide useful mixin methods. + +* ABCs integrate with other features like mypy type-checking, + ``functools.singledispatch``, etc. + +One obvious thing to check is whether this choice affects speed. Using +the attached benchmark script on a CPython 3.7 prerelease (revision +c4d77a661138d, self-compiled, no PGO), on a Thinkpad T450s running +Linux, we find:: + + np.asarray(ndarray_obj) 330 ns + np.asarray([]) 1400 ns + + Attribute check, success 80 ns + Attribute check, failure 80 ns + + ABC, success via subclass 340 ns + ABC, success via register() 700 ns + ABC, failure 370 ns + +Notes: + +* The first two lines are included to put the other lines in context. + +* This used 3.7 because both ``getattr`` and ABCs are receiving + substantial optimizations in this release, and it's more + representative of the long-term future of Python. (Failed + ``getattr`` doesn't necessarily construct an exception object + anymore, and ABCs were reimplemented in C.) + +* The "success" lines refer to cases where ``quacks_like_array`` would + return True. The "failure" lines are cases where it would return + False. + +* The first measurement for ABCs is subclasses defined like:: + + class MyArray(AbstractArray): + ... + + The second is for subclasses defined like:: + + class MyArray: + ... + + AbstractArray.register(MyArray) + + I don't know why there's such a large difference between these. + +In practice, either way we'd only do the full test after first +checking for well-known types like ``ndarray``, ``list``, etc. `This +is how NumPy currently checks for other double-underscore attributes +<https://github.com/numpy/numpy/blob/master/numpy/core/src/private/get_attr_string.h>`__ +and the same idea applies here to either approach. So these numbers +won't affect the common case, just the case where we actually have an +``AbstractArray``, or else another third-party object that will end up +going through ``__array__`` or ``__array_interface__`` or end up as an +object array. + +So in summary, using an ABC will be slightly slower than using an +attribute, but this doesn't affect the most common paths, and the +magnitude of slowdown is fairly small (~250 ns on an operation that +already takes longer than that). Furthermore, we can potentially +optimize this further (e.g. by keeping a tiny LRU cache of types that +are known to be AbstractArray subclasses, on the assumption that most +code will only use one or two of these types at a time), and it's very +unclear that this even matters – if the speed of ``asarray`` no-op +pass-throughs were a bottleneck that showed up in profiles, then +probably we would have made them faster already! (It would be trivial +to fast-path this, but we don't.) + +Given the semantic and usability advantages of ABCs, this seems like +an acceptable trade-off. + +.. + CPython 3.6 (from Debian):: + + Attribute check, success 110 ns + Attribute check, failure 370 ns + + ABC, success via subclass 690 ns + ABC, success via register() 690 ns + ABC, failure 1220 ns + + +Specification of ``asabstractarray`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Given ``AbstractArray``, the definition of ``asabstractarray`` is simple:: + + def asabstractarray(a, dtype=None): + if isinstance(a, AbstractArray): + if dtype is not None and dtype != a.dtype: + return a.astype(dtype) + return a + return asarray(a, dtype=dtype) + +Things to note: + +* ``asarray`` also accepts an ``order=`` argument, but we don't + include that here because it's about details of memory + representation, and the whole point of this function is that you use + it to declare that you don't care about details of memory + representation. + +* Using the ``astype`` method allows the ``a`` object to decide how to + implement casting for its particular type. + +* For strict compatibility with ``asarray``, we skip calling + ``astype`` when the dtype is already correct. Compare:: + + >>> a = np.arange(10) + + # astype() always returns a view: + >>> a.astype(a.dtype) is a + False + + # asarray() returns the original object if possible: + >>> np.asarray(a, dtype=a.dtype) is a + True + + +What exactly are you promising if you inherit from ``AbstractArray``? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This will presumably be refined over time. The ideal of course is that +your class should be indistinguishable from a real ``ndarray``, but +nothing enforces that except the expectations of users. In practice, +declaring that your class implements the ``AbstractArray`` interface +simply means that it will start passing through ``asabstractarray``, +and so by subclassing it you're saying that if some code works for +``ndarray``\s but breaks for your class, then you're willing to accept +bug reports on that. + +To start with, we should declare ``__array_ufunc__`` to be an abstract +method, and add the ``NDArrayOperatorsMixin`` methods as mixin +methods. + +Declaring ``astype`` as an ``@abstractmethod`` probably makes sense as +well, since it's used by ``asabstractarray``. We might also want to go +ahead and add some basic attributes like ``ndim``, ``shape``, +``dtype``. + +Adding new abstract methods will be a bit tricky, because ABCs enforce +these at subclass time; therefore, simply adding a new +`@abstractmethod` will be a backwards compatibility break. If this +becomes a problem then we can use some hacks to implement an +`@upcoming_abstractmethod` decorator that only issues a warning if the +method is missing, and treat it like a regular deprecation cycle. (In +this case, the thing we'd be deprecating is "support for abstract +arrays that are missing feature X".) + + +Naming +~~~~~~ + +The name of the ABC doesn't matter too much, because it will only be +referenced rarely and in relatively specialized situations. The name +of the function matters a lot, because most existing instances of +``asarray`` should be replaced by this, and in the future it's what +everyone should be reaching for by default unless they have a specific +reason to use ``asarray`` instead. This suggests that its name really +should be *shorter* and *more memorable* than ``asarray``... which +is difficult. I've used ``asabstractarray`` in this draft, but I'm not +really happy with it, because it's too long and people are unlikely to +start using it by habit without endless exhortations. + +One option would be to actually change ``asarray``\'s semantics so +that *it* passes through ``AbstractArray`` objects unchanged. But I'm +worried that there may be a lot of code out there that calls +``asarray`` and then passes the result into some C function that +doesn't do any further type checking (because it knows that its caller +has already used ``asarray``). If we allow ``asarray`` to return +``AbstractArray`` objects, and then someone calls one of these C +wrappers and passes it an ``AbstractArray`` object like a sparse +array, then they'll get a segfault. Right now, in the same situation, +``asarray`` will instead invoke the object's ``__array__`` method, or +use the buffer interface to make a view, or pass through an array with +object dtype, or raise an error, or similar. Probably none of these +outcomes are actually desireable in most cases, so maybe making it a +segfault instead would be OK? But it's dangerous given that we don't +know how common such code is. OTOH, if we were starting from scratch +then this would probably be the ideal solution. + +We can't use ``asanyarray`` or ``array``, since those are already +taken. + +Any other ideas? ``np.cast``, ``np.coerce``? + + +Implementation +-------------- + +1. Rename ``NDArrayOperatorsMixin`` to ``AbstractArray`` (leaving + behind an alias for backwards compatibility) and make it an ABC. + +2. Add ``asabstractarray`` (or whatever we end up calling it), and + probably a C API equivalent. + +3. Begin migrating NumPy internal functions to using + ``asabstractarray`` where appropriate. + + +Backward compatibility +---------------------- + +This is purely a new feature, so there are no compatibility issues. +(Unless we decide to change the semantics of ``asarray`` itself.) + + +Rejected alternatives +--------------------- + +One suggestion that has come up is to define multiple abstract classes +for different subsets of the array interface. Nothing in this proposal +stops either NumPy or third-parties from doing this in the future, but +it's very difficult to guess ahead of time which subsets would be +useful. Also, "the full ndarray interface" is something that existing +libraries are written to expect (because they work with actual +ndarrays) and test (because they test with actual ndarrays), so it's +by far the easiest place to start. + + +Links to discussion +------------------- + +* https://mail.python.org/pipermail/numpy-discussion/2018-March/077767.html + + +Appendix: Benchmark script +-------------------------- + +.. literalinclude:: nep-0016-benchmark.py + + +Copyright +--------- + +This document has been placed in the public domain. diff --git a/doc/neps/nep-0016-benchmark.py b/doc/neps/nep-0016-benchmark.py new file mode 100644 index 000000000..ec8e44726 --- /dev/null +++ b/doc/neps/nep-0016-benchmark.py @@ -0,0 +1,48 @@ +import perf +import abc +import numpy as np + +class NotArray: + pass + +class AttrArray: + __array_implementer__ = True + +class ArrayBase(abc.ABC): + pass + +class ABCArray1(ArrayBase): + pass + +class ABCArray2: + pass + +ArrayBase.register(ABCArray2) + +not_array = NotArray() +attr_array = AttrArray() +abc_array_1 = ABCArray1() +abc_array_2 = ABCArray2() + +# Make sure ABC cache is primed +isinstance(not_array, ArrayBase) +isinstance(abc_array_1, ArrayBase) +isinstance(abc_array_2, ArrayBase) + +runner = perf.Runner() +def t(name, statement): + runner.timeit(name, statement, globals=globals()) + +t("np.asarray([])", "np.asarray([])") +arrobj = np.array([]) +t("np.asarray(arrobj)", "np.asarray(arrobj)") + +t("attr, False", + "getattr(not_array, '__array_implementer__', False)") +t("attr, True", + "getattr(attr_array, '__array_implementer__', False)") + +t("ABC, False", "isinstance(not_array, ArrayBase)") +t("ABC, True, via inheritance", "isinstance(abc_array_1, ArrayBase)") +t("ABC, True, via register", "isinstance(abc_array_2, ArrayBase)") + diff --git a/doc/neps/nep-0022-ndarray-duck-typing-overview.rst b/doc/neps/nep-0022-ndarray-duck-typing-overview.rst index 04e4a14b7..480da51a3 100644 --- a/doc/neps/nep-0022-ndarray-duck-typing-overview.rst +++ b/doc/neps/nep-0022-ndarray-duck-typing-overview.rst @@ -3,9 +3,10 @@ NEP 22 — Duck typing for NumPy arrays – high level overview =========================================================== :Author: Stephan Hoyer <shoyer@google.com>, Nathaniel J. Smith <njs@pobox.com> -:Status: Draft +:Status: Accepted :Type: Informational :Created: 2018-03-22 +:Resolution: https://mail.python.org/pipermail/numpy-discussion/2018-September/078752.html Abstract -------- diff --git a/doc/neps/nep-0027-zero-rank-arrarys.rst b/doc/neps/nep-0027-zero-rank-arrarys.rst new file mode 100644 index 000000000..11ea44dbd --- /dev/null +++ b/doc/neps/nep-0027-zero-rank-arrarys.rst @@ -0,0 +1,251 @@ +========================= +NEP 27 — Zero Rank Arrays +========================= + +:Author: Alexander Belopolsky (sasha), transcribed Matt Picus <matti.picus@gmail.com> +:Status: Draft +:Type: Informational +:Created: 2006-06-10 + +Abstract +-------- + +NumPy has both zero rank arrays and scalars. This design document, adapted from +a `2006 wiki entry`_, describes what zero rank arrays are and why they exist. +It was transcribed 2018-10-13 into a NEP and links were updated. + +Note that some of the information here is dated, for instance indexing of 0-D +arrays now is now implemented and does not error. + +Zero-Rank Arrays +---------------- + +Zero-rank arrays are arrays with shape=(). For example: + + >>> x = array(1) + >>> x.shape + () + + +Zero-Rank Arrays and Array Scalars +---------------------------------- + +Array scalars are similar to zero-rank arrays in many aspects:: + + + >>> int_(1).shape + () + +They even print the same:: + + + >>> print int_(1) + 1 + >>> print array(1) + 1 + + +However there are some important differences: + +* Array scalars are immutable +* Array scalars have different python type for different data types + +Motivation for Array Scalars +---------------------------- + +Numpy's design decision to provide 0-d arrays and array scalars in addition to +native python types goes against one of the fundamental python design +principles that there should be only one obvious way to do it. In this section +we will try to explain why it is necessary to have three different ways to +represent a number. + +There were several numpy-discussion threads: + + +* `rank-0 arrays`_ in a 2002 mailing list thread. +* Thoughts about zero dimensional arrays vs Python scalars in a `2005 mailing list thread`_] + +It has been suggested several times that NumPy just use rank-0 arrays to +represent scalar quantities in all case. Pros and cons of converting rank-0 +arrays to scalars were summarized as follows: + +- Pros: + + - Some cases when Python expects an integer (the most + dramatic is when slicing and indexing a sequence: + _PyEval_SliceIndex in ceval.c) it will not try to + convert it to an integer first before raising an error. + Therefore it is convenient to have 0-dim arrays that + are integers converted for you by the array object. + + - No risk of user confusion by having two types that + are nearly but not exactly the same and whose separate + existence can only be explained by the history of + Python and NumPy development. + + - No problems with code that does explicit typechecks + ``(isinstance(x, float)`` or ``type(x) == types.FloatType)``. Although + explicit typechecks are considered bad practice in general, there are a + couple of valid reasons to use them. + + - No creation of a dependency on Numeric in pickle + files (though this could also be done by a special case + in the pickling code for arrays) + +- Cons: + + - It is difficult to write generic code because scalars + do not have the same methods and attributes as arrays. + (such as ``.type`` or ``.shape``). Also Python scalars have + different numeric behavior as well. + + - This results in a special-case checking that is not + pleasant. Fundamentally it lets the user believe that + somehow multidimensional homoegeneous arrays + are something like Python lists (which except for + Object arrays they are not). + +Numpy implements a solution that is designed to have all the pros and none of the cons above. + + Create Python scalar types for all of the 21 types and also + inherit from the three that already exist. Define equivalent + methods and attributes for these Python scalar types. + +The Need for Zero-Rank Arrays +----------------------------- + +Once the idea to use zero-rank arrays to represent scalars was rejected, it was +natural to consider whether zero-rank arrays can be eliminated alltogether. +However there are some important use cases where zero-rank arrays cannot be +replaced by array scalars. See also `A case for rank-0 arrays`_ from February +2006. + +* Output arguments:: + + >>> y = int_(5) + >>> add(5,5,x) + array(10) + >>> x + array(10) + >>> add(5,5,y) + Traceback (most recent call last): + File "<stdin>", line 1, in ? + TypeError: return arrays must be of ArrayType + +* Shared data:: + + >>> x = array([1,2]) + >>> y = x[1:2] + >>> y.shape = () + >>> y + array(2) + >>> x[1] = 20 + >>> y + array(20) + +Indexing of Zero-Rank Arrays +---------------------------- + +As of NumPy release 0.9.3, zero-rank arrays do not support any indexing:: + + >>> x[...] + Traceback (most recent call last): + File "<stdin>", line 1, in ? + IndexError: 0-d arrays can't be indexed. + +On the other hand there are several cases that make sense for rank-zero arrays. + +Ellipsis and empty tuple +~~~~~~~~~~~~~~~~~~~~~~~~ + +Sasha started a `Jan 2006 discussion`_ on scipy-dev +with the following proposal: + + ... it may be reasonable to allow ``a[...]``. This way + ellipsis can be interpereted as any number of ``:`` s including zero. + Another subscript operation that makes sense for scalars would be + ``a[...,newaxis]`` or even ``a[{newaxis, }* ..., {newaxis,}*]``, where + ``{newaxis,}*`` stands for any number of comma-separated newaxis tokens. + This will allow one to use ellipsis in generic code that would work on + any numpy type. + +Francesc Altet supported the idea of ``[...]`` on zero-rank arrays and +`suggested`_ that ``[()]`` be supported as well. + +Francesc's proposal was:: + + In [65]: type(numpy.array(0)[...]) + Out[65]: <type 'numpy.ndarray'> + + In [66]: type(numpy.array(0)[()]) # Indexing a la numarray + Out[66]: <type 'int32_arrtype'> + + In [67]: type(numpy.array(0).item()) # already works + Out[67]: <type 'int'> + +There is a consensus that for a zero-rank array ``x``, both ``x[...]`` and ``x[()]`` should be valid, but the question +remains on what should be the type of the result - zero rank ndarray or ``x.dtype``? + +(Sasha) + First, whatever choice is made for ``x[...]`` and ``x[()]`` they should be + the same because ``...`` is just syntactic sugar for "as many `:` as + necessary", which in the case of zero rank leads to ``... = (:,)*0 = ()``. + Second, rank zero arrays and numpy scalar types are interchangeable within + numpy, but numpy scalars can be use in some python constructs where ndarrays + can't. For example:: + + >>> (1,)[array(0)] + Traceback (most recent call last): + File "<stdin>", line 1, in ? + TypeError: tuple indices must be integers + >>> (1,)[int32(0)] + 1 + +Since most if not all numpy function automatically convert zero-rank arrays to scalars on return, there is no reason for +``[...]`` and ``[()]`` operations to be different. + +See SVN changeset 1864 (which became git commit `9024ff0`_) for +implementation of ``x[...]`` and ``x[()]`` returning numpy scalars. + +See SVN changeset 1866 (which became git commit `743d922`_) for +implementation of ``x[...] = v`` and ``x[()] = v`` + +Increasing rank with newaxis +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Everyone who commented liked this feature, so as of SVN changeset 1871 (which became git commit `b32744e`_) any number of ellipses and +newaxis tokens can be placed as a subscript argument for a zero-rank array. For +example:: + + >>> x = array(1) + >>> x[newaxis,...,newaxis,...] + array([[1]]) + +It is not clear why more than one ellipsis should be allowed, but this is the +behavior of higher rank arrays that we are trying to preserve. + +Refactoring +~~~~~~~~~~~ + +Currently all indexing on zero-rank arrays is implemented in a special ``if (nd +== 0)`` branch of code that used to always raise an index error. This ensures +that the changes do not affect any existing usage (except, the usage that +relies on exceptions). On the other hand part of motivation for these changes +was to make behavior of ndarrays more uniform and this should allow to +eliminate ``if (nd == 0)`` checks alltogether. + +Copyright +--------- + +The original document appeared on the scipy.org wiki, with no Copyright notice, and its `history`_ attributes it to sasha. + +.. _`2006 wiki entry`: https://web.archive.org/web/20100503065506/http://projects.scipy.org:80/numpy/wiki/ZeroRankArray +.. _`history`: https://web.archive.org/web/20100503065506/http://projects.scipy.org:80/numpy/wiki/ZeroRankArray?action=history +.. _`2005 mailing list thread`: https://sourceforge.net/p/numpy/mailman/message/11299166 +.. _`suggested`: https://mail.python.org/pipermail/numpy-discussion/2006-January/005572.html +.. _`Jan 2006 discussion`: https://mail.python.org/pipermail/numpy-discussion/2006-January/005579.html +.. _`A case for rank-0 arrays`: https://mail.python.org/pipermail/numpy-discussion/2006-February/006384.html +.. _`rank-0 arrays`: https://mail.python.org/pipermail/numpy-discussion/2002-September/001600.html +.. _`9024ff0`: https://github.com/numpy/numpy/commit/9024ff0dc052888b5922dde0f3e615607a9e99d7 +.. _`743d922`: https://github.com/numpy/numpy/commit/743d922bf5893acf00ac92e823fe12f460726f90 +.. _`b32744e`: https://github.com/numpy/numpy/commit/b32744e3fc5b40bdfbd626dcc1f72907d77c01c4 diff --git a/doc/release/1.16.0-notes.rst b/doc/release/1.16.0-notes.rst index f2c8f8dc2..f463ff28c 100644 --- a/doc/release/1.16.0-notes.rst +++ b/doc/release/1.16.0-notes.rst @@ -227,4 +227,14 @@ c-extension module. `numpy.ndarray.getfield` now checks the dtype and offset arguments to prevent accessing invalid memory locations. +NumPy functions now support overrides with ``__array_function__`` +----------------------------------------------------------------- +It is now possible to override the implementation of almost all NumPy functions +on non-NumPy arrays by defining a ``__array_function__`` method, as described +in `NEP 18`_. The sole exception are functions for explicitly casting to NumPy +arrays such as ``np.array``. As noted in the NEP, this feature remains +experimental and the details of how to implement such overrides may change in +the future. + .. _`NEP 15` : http://www.numpy.org/neps/nep-0015-merge-multiarray-umath.html +.. _`NEP 18` : http://www.numpy.org/neps/nep-0018-array-function-protocol.html diff --git a/numpy/core/include/numpy/ndarrayobject.h b/numpy/core/include/numpy/ndarrayobject.h index 12fc7098c..45f008b1d 100644 --- a/numpy/core/include/numpy/ndarrayobject.h +++ b/numpy/core/include/numpy/ndarrayobject.h @@ -5,13 +5,7 @@ #ifndef NPY_NDARRAYOBJECT_H #define NPY_NDARRAYOBJECT_H #ifdef __cplusplus -#define CONFUSE_EMACS { -#define CONFUSE_EMACS2 } -extern "C" CONFUSE_EMACS -#undef CONFUSE_EMACS -#undef CONFUSE_EMACS2 -/* ... otherwise a semi-smart identer (like emacs) tries to indent - everything when you're typing */ +extern "C" { #endif #include <Python.h> diff --git a/numpy/core/include/numpy/npy_3kcompat.h b/numpy/core/include/numpy/npy_3kcompat.h index 808518266..a3c69f44e 100644 --- a/numpy/core/include/numpy/npy_3kcompat.h +++ b/numpy/core/include/numpy/npy_3kcompat.h @@ -69,6 +69,16 @@ static NPY_INLINE int PyInt_Check(PyObject *op) { #define Npy_EnterRecursiveCall(x) Py_EnterRecursiveCall(x) #endif +/* Py_SETREF was added in 3.5.2, and only if Py_LIMITED_API is absent */ +#if PY_VERSION_HEX < 0x03050200 + #define Py_SETREF(op, op2) \ + do { \ + PyObject *_py_tmp = (PyObject *)(op); \ + (op) = (op2); \ + Py_DECREF(_py_tmp); \ + } while (0) +#endif + /* * PyString -> PyBytes */ @@ -141,20 +151,14 @@ static NPY_INLINE int PyInt_Check(PyObject *op) { static NPY_INLINE void PyUnicode_ConcatAndDel(PyObject **left, PyObject *right) { - PyObject *newobj; - newobj = PyUnicode_Concat(*left, right); - Py_DECREF(*left); + Py_SETREF(*left, PyUnicode_Concat(*left, right)); Py_DECREF(right); - *left = newobj; } static NPY_INLINE void PyUnicode_Concat2(PyObject **left, PyObject *right) { - PyObject *newobj; - newobj = PyUnicode_Concat(*left, right); - Py_DECREF(*left); - *left = newobj; + Py_SETREF(*left, PyUnicode_Concat(*left, right)); } /* diff --git a/numpy/core/src/common/npy_longdouble.c b/numpy/core/src/common/npy_longdouble.c index 508fbceac..561f4b825 100644 --- a/numpy/core/src/common/npy_longdouble.c +++ b/numpy/core/src/common/npy_longdouble.c @@ -1,17 +1,11 @@ #include <Python.h> #define NPY_NO_DEPRECATED_API NPY_API_VERSION +#define _MULTIARRAYMODULE + #include "numpy/ndarraytypes.h" #include "numpy/npy_math.h" - -/* This is a backport of Py_SETREF */ -#define NPY_SETREF(op, op2) \ - do { \ - PyObject *_py_tmp = (PyObject *)(op); \ - (op) = (op2); \ - Py_DECREF(_py_tmp); \ - } while (0) - +#include "npy_pycompat.h" /* * Heavily derived from PyLong_FromDouble @@ -66,7 +60,7 @@ npy_longdouble_to_PyLong(npy_longdouble ldval) npy_ulonglong chunk = (npy_ulonglong)frac; PyObject *l_chunk; /* v = v << chunk_size */ - NPY_SETREF(v, PyNumber_Lshift(v, l_chunk_size)); + Py_SETREF(v, PyNumber_Lshift(v, l_chunk_size)); if (v == NULL) { goto done; } @@ -77,7 +71,7 @@ npy_longdouble_to_PyLong(npy_longdouble ldval) goto done; } /* v = v | chunk */ - NPY_SETREF(v, PyNumber_Or(v, l_chunk)); + Py_SETREF(v, PyNumber_Or(v, l_chunk)); Py_DECREF(l_chunk); if (v == NULL) { goto done; @@ -90,7 +84,7 @@ npy_longdouble_to_PyLong(npy_longdouble ldval) /* v = -v */ if (neg) { - NPY_SETREF(v, PyNumber_Negative(v)); + Py_SETREF(v, PyNumber_Negative(v)); if (v == NULL) { goto done; } diff --git a/numpy/lib/tests/test_histograms.py b/numpy/lib/tests/test_histograms.py index a71060a46..fa6ad989f 100644 --- a/numpy/lib/tests/test_histograms.py +++ b/numpy/lib/tests/test_histograms.py @@ -119,6 +119,13 @@ class TestHistogram(object): h, b = histogram(a, bins=8, range=[1, 9], weights=w) assert_equal(h, w[1:-1]) + def test_arr_weights_mismatch(self): + a = np.arange(10) + .5 + w = np.arange(11) + .5 + with assert_raises_regex(ValueError, "same shape as"): + h, b = histogram(a, range=[1, 9], weights=w, density=True) + + def test_type(self): # Check the type of the returned histogram a = np.arange(10) + .5 diff --git a/numpy/lib/tests/test_utils.py b/numpy/lib/tests/test_utils.py index c27c3cbf5..2723f3440 100644 --- a/numpy/lib/tests/test_utils.py +++ b/numpy/lib/tests/test_utils.py @@ -56,10 +56,34 @@ def test_safe_eval_nameconstant(): utils.safe_eval('None') -def test_byte_bounds(): - a = arange(12).reshape(3, 4) - low, high = utils.byte_bounds(a) - assert_equal(high - low, a.size * a.itemsize) +class TestByteBounds(object): + + def test_byte_bounds(self): + # pointer difference matches size * itemsize + # due to contiguity + a = arange(12).reshape(3, 4) + low, high = utils.byte_bounds(a) + assert_equal(high - low, a.size * a.itemsize) + + def test_unusual_order_positive_stride(self): + a = arange(12).reshape(3, 4) + b = a.T + low, high = utils.byte_bounds(b) + assert_equal(high - low, b.size * b.itemsize) + + def test_unusual_order_negative_stride(self): + a = arange(12).reshape(3, 4) + b = a.T[::-1] + low, high = utils.byte_bounds(b) + assert_equal(high - low, b.size * b.itemsize) + + def test_strided(self): + a = arange(12) + b = a[::2] + low, high = utils.byte_bounds(b) + # the largest pointer address is lost (even numbers only in the + # stride), and compensate addresses for striding by 2 + assert_equal(high - low, b.size * 2 * b.itemsize - b.itemsize) def test_assert_raises_regex_context_manager(): |