numpy
Advanced tools
| * StringDType comparisons now correctly handle embedded NULL bytes. |
| .. _basics.performant_code: | ||
| *************************************************** | ||
| Writing Performant NumPy Code with Multi-Core CPUs | ||
| *************************************************** | ||
| Introduction | ||
| ================ | ||
| NumPy is designed for high performance numerical computing in Python by leveraging vectorized operations. | ||
| However, vectorization does not always fully utilize the capabilities of multi-core processors. | ||
| To exploit parallelism, additional strategies are necessary. | ||
| In this section, we cover the following topics: | ||
| * :ref:`General concepts for using multi-core processors in Python <basics.performant_code.general_concepts_for_multi_core_processors>` | ||
| * :ref:`Using multi-core processors with Python standard libraries <basics.performant_code.multi_core_with_standard_libraries>` | ||
| * :ref:`Third party libraries for multi-core processing <basics.performant_code.third_party_libraries>` | ||
| .. _basics.performant_code.general_concepts_for_multi_core_processors: | ||
| General concepts for multi-core processors in Python | ||
| ===================================================== | ||
| Multiprocessing | ||
| ---------------- | ||
| Multiprocessing is a technique that allows the execution of multiple processes simultaneously, | ||
| each with its own Python interpreter and memory space. | ||
| As a high-level API, Python provides the `concurrent.futures.ProcessPoolExecutor` class | ||
| to facilitate multiprocessing. | ||
| Firstly, we introduce brief Pros and Cons of multiprocessing: | ||
| Pros | ||
| ++++ | ||
| * Bypasses the Global Interpreter Lock (GIL), allowing true parallelism | ||
| * Avoids accidental data sharing due to separate memory spaces | ||
| Cons | ||
| ++++ | ||
| * Higher memory usage due to separate memory spaces for each process | ||
| * Difficulty in sharing data between processes, requiring serialization (pickling) of objects | ||
| General tips | ||
| ++++++++++++ | ||
| The following are general tips for utilizing multiprocessing. | ||
| Some of these tips are used in the `Multiprocessing Example <#multiprocessing-example>`__. | ||
| Reduce creation overhead | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| Process creation has a higher overhead compared to thread creation due to the need to initialize a new Python interpreter and memory space. | ||
| To mitigate this overhead, consider the following strategies: | ||
| * Use process pools to reuse existing processes instead of creating new ones for each task. | ||
| `concurrent.futures.ProcessPoolExecutor` provides this feature. | ||
| * Select appropriate startup methods. Avoid explicitly selecting ``fork`` | ||
| unless you know that it is safe in your application. | ||
| Forking a multithreaded process is problematic and | ||
| can lead to deadlocks or crashes. | ||
| Python 3.14 changed the default start method on POSIX platforms | ||
| from ``fork`` to ``forkserver`` to avoid common multithreaded process | ||
| incompatibilities. | ||
| See the `multiprocessing documentation <https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods>`__ for more details. | ||
| Reduce communication overhead | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| Inter-process communication (IPC) can introduce significant overhead due to data serialization and transfer between processes. In Python, only picklable objects are allowed to be passed between processes. | ||
| Due to this limitation, multiprocessing is not suitable for programs which need to serialize data between processes frequently. | ||
| To reduce communication overhead, consider the following strategies: | ||
| * Minimize the amount of data transferred between processes. | ||
| * Use shared memory constructs such as `multiprocessing.shared_memory`, `multiprocessing.Array` | ||
| or `multiprocessing.Value` for large data that needs to be accessed by multiple processes. | ||
| * `Balance processing load <#balance-processing-load>`__ to ensure | ||
| that all processes are utilized efficiently and avoid idle time. | ||
| Pickling considerations | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| The worker function and its arguments must be picklable when using multiprocessing. | ||
| This requirement can become a limitation when working with complex data structures or dynamically | ||
| defined functions. | ||
| If you encounter pickling-related issues, consider the following strategies: | ||
| * Refactor your code to use simpler data structures or functions. | ||
| For example, define worker functions at the top level of a module and avoid lambda or nested functions. | ||
| * Consider third-party libraries such as `joblib <https://github.com/joblib/joblib>`__. | ||
| ``joblib``'s default backend ``loky`` relies on `cloudpickle <https://github.com/cloudpipe/cloudpickle>`__ | ||
| for serialization and can handle a wider range of Python objects than the standard ``pickle`` module. | ||
| See the ``joblib`` documentaion on `Serialization of un-picklable objects <https://joblib.readthedocs.io/en/latest/auto_examples/serialization_and_wrappers.html>`__ for more details. | ||
| Multithreading | ||
| ----------------- | ||
| Multithreading allows multiple threads to run within the same process, | ||
| sharing the same memory space. | ||
| Free-threaded Python was introduced experimentally in Python 3.13 | ||
| and became a supported (non-experimental) feature in Python 3.14. | ||
| When combined with libraries that are explicitly designed to be thread-safe, | ||
| this can enable true parallel execution with threads. | ||
| For details on free-threaded Python builds, see the | ||
| `Python Free-Threading Guide <https://py-free-threading.github.io/>`__. | ||
| As a high-level API, Python provides the `concurrent.futures.ThreadPoolExecutor` class | ||
| for thread-based parallelism. | ||
| Python also provides the | ||
| `concurrent.futures.InterpreterPoolExecutor <https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.InterpreterPoolExecutor>`__, | ||
| which uses multiple interpreters running in separate threads | ||
| and avoids sharing Python objects between them. | ||
| However, it is not yet available in NumPy. | ||
| (See `gh-24755 <https://github.com/numpy/numpy/issues/24755>`__ for details.) | ||
| The main pros and cons of multithreading are as follows: | ||
| Pros | ||
| ++++ | ||
| * Lower memory usage since threads share the same memory space | ||
| * Easier communication between threads | ||
| Cons | ||
| ++++ | ||
| * Possibility of race conditions when mutating shared data simultaneously with reads in other threads | ||
| * Limited performance improvement if using Python libraries are not thread-safe or have limited support for free-threaded Python builds | ||
| General tips | ||
| ++++++++++++ | ||
| The following are general tips for utilizing multithreading. | ||
| For more details on thread safety guarantees for built-in types | ||
| in Python's free-threaded build, see the Python documentation | ||
| on `Thread Safety Guarantees <https://docs.python.org/3.15/library/threadsafety.html#thread-safety-guarantees>`__. | ||
| Some of these tips are used in the `Multithreading Example <#multithreading-example>`__. | ||
| Avoid race conditions | ||
| ~~~~~~~~~~~~~~~~~~~~~ | ||
| Race conditions occur when multiple threads update shared data simultaneously, | ||
| leading to unpredictable results. | ||
| To avoid race conditions, consider the following strategies: | ||
| * Minimize the amount of shared data between threads by designing your program | ||
| to use thread-local storage or by passing data explicitly to threads. | ||
| * Prefer immutable NumPy arrays or read-only access patterns when possible, | ||
| since they reduce the need for explicit synchronization. | ||
| * Use thread-safe data structures or synchronization primitives like locks, semaphores, | ||
| or condition variables to manage access to shared data. | ||
| Note that improper use of these synchronization mechanisms can cause deadlocks, | ||
| so they should be used with care. | ||
| Avoid CPU oversubscription | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| Some NumPy operations, such as matrix multiplication and linear algebra functions | ||
| (See :ref:`Linear Algebra <routines.linalg>`), | ||
| may use multiple threads provided | ||
| by the underlying BLAS library (e.g. OpenBLAS, MKL). | ||
| If these operations are executed from another thread pool that already uses | ||
| all available CPU cores, CPU oversubscription can occur. In this situation, | ||
| both the outer thread pool and the BLAS threads compete for the same CPU | ||
| resources, which can reduce performance. | ||
| To avoid CPU oversubscription, consider the following strategies: | ||
| * Limit the number of threads in BLAS to 1, for example using | ||
| `threadpoolctl <https://github.com/joblib/threadpoolctl>`__ | ||
| Common tips for both multiprocessing and multithreading | ||
| ------------------------------------------------------- | ||
| Balance processing load | ||
| +++++++++++++++++++++++ | ||
| If the processing load is not evenly distributed among workers, | ||
| some workers may finish their tasks earlier and remain idle while others are still working. | ||
| It leads to inefficient use of resources and longer overall execution time. | ||
| To achieve better load balancing, consider the following strategies: | ||
| * Use dynamic task allocation where tasks are assigned to workers as they become available, | ||
| rather than pre-allocating tasks. | ||
| * Check ``chunksize`` parameter to ensure that tasks are neither too small (causing excessive overhead) | ||
| nor too large (leading to load imbalance). | ||
| Determine the correct number of cpus | ||
| +++++++++++++++++++++++++++++++++++++ | ||
| Pythons provides `os.cpu_count` and `os.process_cpu_count <https://docs.python.org/3/library/os.html#os.process_cpu_count>`__ | ||
| functions to get the number of CPUs in the system and the current process, respectively. | ||
| However, in some environments (e.g., Docker containers or HPC clusters), | ||
| this may not reflect the actual number of CPUs available to the process. | ||
| To get a more accurate count of available CPUs, consider the following strategies: | ||
| * Use `joblib.cpu_count() <https://joblib.readthedocs.io/en/latest/generated/joblib.cpu_count.html>`__, | ||
| which takes into account constraints such as CPU affinity settings and Linux CFS scheduler quotas. | ||
| (See `joblib <#joblib>`__ section for more details about joblib.) | ||
| .. _basics.performant_code.multi_core_with_standard_libraries: | ||
| Using multi-core processors with Python standard libraries | ||
| ============================================================= | ||
| In this section, we demonstrate how to use Python's standard libraries to leverage multi-core processors with NumPy. | ||
| As an example, we use `Mandelbrot set <https://en.wikipedia.org/wiki/Mandelbrot_set>`__ generation. | ||
| Mandelbrot set is defined as the set of complex numbers ``c`` | ||
| for which the sequence defined by the iterative function does not diverge to infinity: | ||
| .. math:: | ||
| z_{n+1} = z_n^2 + c, \quad z_0 = 0 | ||
| If the absolute value of :math:`z_n` remains bounded | ||
| (i.e., does not exceed a certain threshold, typically ``2`` ) after a fixed number of iterations, | ||
| then ``c`` is considered to be in the Mandelbrot set. | ||
| Following to this definition, we can calculate each point in the complex plane independently, | ||
| making it suited for parallel computation. | ||
| The hot colors in the image below represent the number of iterations | ||
| it took for the sequence to diverge for each point in the complex plane. | ||
| .. image:: images/np_mandelbrot.png | ||
| :alt: Mandelbrot set | ||
| :align: center | ||
| :width: 500px | ||
| Multiprocessing Example | ||
| ------------------------ | ||
| The following code demonstrates how to use `concurrent.futures.ProcessPoolExecutor` | ||
| to parallelize the Mandelbrot set generation across multiple processes. | ||
| This example prioritizes clarity over efficiency. | ||
| In practice, transferring large NumPy arrays between processes can be expensive. | ||
| Defining shared-memory arrays or creating arrays within each process may be more efficient implementation. | ||
| .. code-block:: python | ||
| from concurrent.futures import ProcessPoolExecutor | ||
| import numpy as np | ||
| from numpy.typing import NDArray | ||
| def mandelbrot_block( | ||
| c_block: NDArray[np.complex128], max_iter: int | ||
| ) -> NDArray[np.int64]: | ||
| z = np.zeros(c_block.shape, dtype=np.complex128) | ||
| steps = np.zeros(c_block.shape, dtype=np.int64) | ||
| for _ in range(max_iter): | ||
| mask = np.abs(z) <= 2 | ||
| z[mask] = z[mask] * z[mask] + c_block[mask] | ||
| steps[mask] += 1 | ||
| return steps | ||
| def mandelbrot_set( | ||
| arr: NDArray[np.complex128], | ||
| max_iter: int, | ||
| n_workers: int, | ||
| ) -> NDArray[np.int64]: | ||
| n_workers = min(n_workers, arr.size) | ||
| arrs = np.array_split(arr, n_workers) | ||
| with ProcessPoolExecutor(max_workers=n_workers) as pool: | ||
| futures = [ | ||
| pool.submit(mandelbrot_block, _arr, max_iter) for _arr in arrs | ||
| ] | ||
| results = [future.result() for future in futures] | ||
| return np.concatenate(results) | ||
| if __name__ == '__main__': | ||
| xmin, xmax, ymin, ymax = -2.0, 1.0, -1.5, 1.5 | ||
| nx, ny = 800, 800 | ||
| max_iter = 10000 | ||
| n_workers = 10 | ||
| real = np.linspace(xmin, xmax, nx, dtype=np.float64) | ||
| imag = np.linspace(ymin, ymax, ny, dtype=np.float64) | ||
| arr = (real[:, np.newaxis] + 1j * imag[np.newaxis, :]).ravel() | ||
| mandelbrot_image = mandelbrot_set(arr, max_iter, n_workers) | ||
| mandelbrot_image = mandelbrot_image.reshape((nx, ny)) | ||
| Multithreading Example | ||
| ---------------------- | ||
| As in the multiprocessing example, we demonstrate how to use `concurrent.futures.ThreadPoolExecutor` | ||
| to parallelize the Mandelbrot set generation across multiple threads. | ||
| For more detailed explanations and additional examples, | ||
| see `Examples Demonstrating Free-Threaded Python <https://py-free-threading.github.io/examples/>`__. | ||
| Setup | ||
| +++++ | ||
| Install a free-threaded build Python | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| Before running the multithreading example, ensure you have | ||
| a free-threaded build of Python 3.13 or later. | ||
| About how to install a free-threaded build of Python, | ||
| please refer to the `Installing Free-Threaded Python <https://py-free-threading.github.io/installing-cpython/>`__. | ||
| According to the `Python documentation <https://docs.python.org/3/howto/free-threading-python.html>`__, | ||
| there are several ways to verify if your Python build is free-threaded. | ||
| * Run ``python -VV`` in your terminal and check ``free-threading build`` is shown | ||
| * Check the value of `sys._is_gil_enabled()` in a Python shell, which should return `False`. | ||
| Code Example | ||
| ++++++++++++ | ||
| The following code demonstrates how to use `concurrent.futures.ThreadPoolExecutor` | ||
| to parallelize the Mandelbrot set generation across multiple threads. | ||
| This implementation shares several arrays between threads. | ||
| For example, ``SHARED_readonly_arr`` is a read-only array that holds the complex numbers to be evaluated, | ||
| and ``SHARED_updating_steps`` is an array that holds the number of iterations for each point. | ||
| .. code-block:: python | ||
| import sys | ||
| from concurrent.futures import ThreadPoolExecutor | ||
| import numpy as np | ||
| def mandelbrot_block(start: int, stop: int, max_iter: int) -> None: | ||
| z_target = np.zeros(stop - start, dtype=np.complex128) | ||
| indexes = slice(start, stop) | ||
| arr_target = SHARED_readonly_arr[indexes] | ||
| steps_target = SHARED_updating_steps[indexes] | ||
| threshold = 2.0 | ||
| for _ in range(max_iter): | ||
| mask = np.abs(z_target) <= threshold | ||
| z_target[mask] = z_target[mask] * z_target[mask] + arr_target[mask] | ||
| steps_target[mask] += 1 | ||
| SHARED_updating_steps[indexes] = steps_target | ||
| return None | ||
| def mandelbrot_set( | ||
| total_size: int, | ||
| max_iter: int, | ||
| n_workers: int, | ||
| ) -> None: | ||
| chunksize = total_size // n_workers | ||
| with ThreadPoolExecutor(max_workers=n_workers) as pool: | ||
| futures = [ | ||
| pool.submit( | ||
| mandelbrot_block, start, min(start + chunksize, total_size), max_iter | ||
| ) | ||
| for start in range(0, total_size, chunksize) | ||
| ] | ||
| _ = [future.result() for future in futures] | ||
| if __name__ == '__main__': | ||
| print("Python version is free-threaded:", not sys._is_gil_enabled()) | ||
| assert not sys._is_gil_enabled() | ||
| xmin, xmax, ymin, ymax = -2.0, 1.0, -1.5, 1.5 | ||
| nx, ny = 800, 800 | ||
| max_iter = 10000 | ||
| n_workers = 10 | ||
| real = np.linspace(xmin, xmax, nx, dtype=np.float64) | ||
| imag = np.linspace(ymin, ymax, ny, dtype=np.float64) | ||
| SHARED_readonly_arr = (real[:, np.newaxis] + 1j * imag[np.newaxis, :]).ravel() | ||
| SHARED_readonly_arr.flags.writeable = False | ||
| SHARED_updating_steps = np.zeros(SHARED_readonly_arr.shape, dtype=np.int64) | ||
| mandelbrot_set(SHARED_readonly_arr.size, max_iter, n_workers) | ||
| mandelbrot_image = SHARED_updating_steps.reshape((nx, ny)) | ||
| .. _basics.performant_code.third_party_libraries: | ||
| Third Party Libraries for Multi-Core Processing | ||
| =============================================== | ||
| In many practical scenarios, third-party libraries can provide more convenient and efficient solutions | ||
| than using Python's standard libraries. | ||
| Dask | ||
| ---- | ||
| Dask is an open-source library that provides parallel compuing features | ||
| not only for a single machine but also for a cluster of machines. | ||
| It also provides ``DaskArray`` which has a similar API to NumPy's ``ndarray``. | ||
| If you are familiar with NumPy, you can easily get started with ``DaskArray``. | ||
| * Dask Documentaion: https://docs.dask.org/en/stable/ | ||
| * Dask GitHub Repository: https://github.com/dask/dask | ||
| joblib | ||
| ------ | ||
| ``joblib`` is a library that provides helper functions | ||
| which make it easy to parallelize tasks. | ||
| For example, | ||
| * ``joblib``'s default backend ``loky`` relies on `cloudpickle <https://github.com/cloudpipe/cloudpickle>`__ | ||
| for serialization and can handle a wider range of Python objects than the standard ``pickle`` module. | ||
| (e.g., lambda functions) | ||
| * `joblib.cpu_count() <https://joblib.readthedocs.io/en/latest/generated/joblib.cpu_count.html>`__ | ||
| returns the number of CPUs available to the current process, taking into | ||
| account constraints such as CPU affinity settings and Linux CFS scheduler | ||
| quotas. This may provide a more accurate value than | ||
| `os.cpu_count` and `os.process_cpu_count <https://docs.python.org/3/library/os.html#os.process_cpu_count>`__ | ||
| functions in Docker containers and other resource-constrained environments. | ||
| For more details on ``joblib``, see the following resources: | ||
| * joblib Documentation: https://joblib.readthedocs.io/en/latest/ | ||
| * joblib GitHub Repository: https://github.com/joblib/joblib | ||
| threadpoolctl | ||
| -------------- | ||
| ``threadpoolctl`` is a library that provides utilities to control the behavior | ||
| of thread pools in Python, including other thread pools used by libraries | ||
| such as BLAS and OpenMP. | ||
| It allows you to avoid CPU oversubscription | ||
| when using multiple libraries that utilize threads. | ||
| For more details on ``threadpoolctl``, see the following resources: | ||
| * threadpoolctl GitHub Repository: https://github.com/joblib/threadpoolctl |
Sorry, the diff of this file is not supported yet
@@ -38,6 +38,7 @@ name: Test Emscripten/Pyodide build | ||
| persist-credentials: false | ||
| - uses: pypa/cibuildwheel@8d2b08b68458a16aeb24b64e68a09ab1c8e82084 # v3.4.1 | ||
| - uses: pypa/cibuildwheel@294735312765b09d24a2fbec22660ce817587d55 # v4.1.0 | ||
| env: | ||
| CIBW_PLATFORM: pyodide | ||
| CIBW_BUILD: cp312-* | ||
| CIBW_BUILD: cp314-pyodide_wasm32 | ||
| CIBW_ENABLE: pyodide-prerelease | ||
| CIBW_BUILD_VERBOSITY: 3 |
@@ -117,5 +117,6 @@ # To update pinned container digests and uv version: not handled by Dependabot. | ||
| grep -v ninja /numpy/requirements/build_requirements.txt > /tmp/build_requirements.txt && | ||
| grep -v ninja /numpy/requirements/test_requirements.txt > /tmp/test_requirements.txt && | ||
| uv venv --python 3.12 .venv && | ||
| source .venv/bin/activate && | ||
| uv pip install -r /tmp/build_requirements.txt pytest pytest-xdist hypothesis pytest-timeout | ||
| uv pip install -r /tmp/build_requirements.txt -r /tmp/test_requirements.txt | ||
| rm -f /usr/local/bin/ninja && mkdir -p /usr/local/bin && ln -s /host/usr/bin/ninja /usr/local/bin/ninja | ||
@@ -224,7 +225,10 @@ " | ||
| grep -v ninja /numpy/requirements/build_requirements.txt > /tmp/build_requirements.txt && | ||
| python -m pip install --break-system-packages uv --extra-index-url https://mirrors.loong64.com/pypi/simple && | ||
| grep -v ninja /numpy/requirements/test_requirements.txt > /tmp/test_requirements.txt && | ||
| python -m pip install --break-system-packages \ | ||
| --extra-index-url https://mirrors.loong64.com/pypi/simple \ | ||
| --only-binary=":all:" uv && | ||
| export PATH="/root/.local/bin:$PATH" && | ||
| uv venv --python 3.12 .venv && | ||
| source .venv/bin/activate && | ||
| uv pip install -r /tmp/build_requirements.txt pytest pytest-xdist hypothesis && | ||
| uv pip install -r /tmp/build_requirements.txt -r /tmp/test_requirements.txt && | ||
| rm -f /usr/local/bin/ninja && mkdir -p /usr/local/bin && ln -s /host/usr/bin/ninja /usr/local/bin/ninja | ||
@@ -231,0 +235,0 @@ " |
@@ -25,3 +25,3 @@ name: Type-checking | ||
| - '.devcontainer/**' | ||
| - '.spin/**' | ||
| - '.spin/LICENSE' | ||
| - 'benchmarks/**' | ||
@@ -58,3 +58,3 @@ - 'branding/**' | ||
| - [ubuntu-latest, '3.13'] | ||
| - [windows-latest, '3.12'] | ||
| - [windows-2022, '3.12'] | ||
| steps: | ||
@@ -61,0 +61,0 @@ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 |
@@ -105,3 +105,3 @@ # Workflow to build and test wheels, similarly to numpy/numpy-release. | ||
| - name: Build wheels | ||
| uses: pypa/cibuildwheel@8d2b08b68458a16aeb24b64e68a09ab1c8e82084 # v3.4.1 | ||
| uses: pypa/cibuildwheel@294735312765b09d24a2fbec22660ce817587d55 # v4.1.0 | ||
| env: | ||
@@ -108,0 +108,0 @@ CIBW_BUILD: ${{ matrix.python }}-${{ matrix.buildplat[1] }} |
@@ -72,4 +72,4 @@ name: Windows tests | ||
| #======================================================================================= | ||
| msvc_python32bit_no_openblas: | ||
| name: MSVC, ${{ matrix.architecture }}, fast, no BLAS | ||
| msvc_python_x86_arm64_no_openblas: | ||
| name: MSVC, ${{ matrix.os }} ${{ matrix.architecture }}, full, no BLAS, ${{ matrix.python_version }} | ||
| runs-on: ${{ matrix.os }} | ||
@@ -82,4 +82,6 @@ strategy: | ||
| architecture: x86 | ||
| python_version: '3.15t-dev' | ||
| - os: windows-11-arm | ||
| architecture: arm64 | ||
| python_version: '3.12' | ||
| # To enable this job on a fork, comment out: | ||
@@ -98,3 +100,3 @@ if: github.repository == 'numpy/numpy' | ||
| with: | ||
| python-version: '3.12' | ||
| python-version: ${{ matrix.python_version }} | ||
| architecture: ${{ matrix.architecture }} | ||
@@ -122,6 +124,6 @@ | ||
| - name: Run test suite (fast) | ||
| - name: Run test suite (full) | ||
| run: | | ||
| cd tools | ||
| python -m pytest --pyargs numpy -m "not slow" -n2 --timeout=600 --durations=10 | ||
| python -m pytest --pyargs numpy -n auto --timeout=600 --durations=10 | ||
@@ -128,0 +130,0 @@ #======================================================================================= |
+1
-0
@@ -626,2 +626,3 @@ # Prevent git from showing duplicate names with commands like "git shortlog" | ||
| Nikita Zamuldinov <59732804+NIK-TIGER-BILL@users.noreply.github.com> <NIK-TIGER-BILL@users.noreply.github.com> | ||
| Nishidh <xnishidh.codes@gmail.com> | ||
| Nyakku Shigure <sigure.qaq@gmail.com> | ||
@@ -628,0 +629,0 @@ Norwid Behrnd <nbehrnd@yahoo.com> |
@@ -107,1 +107,6 @@ @import url('https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;0,900;1,400;1,700;1,900&family=Open+Sans:ital,wght@0,400;0,600;1,400;1,600&display=swap'); | ||
| } | ||
| code.literal, | ||
| code.literal .pre { | ||
| font-variant-ligatures: none; | ||
| } |
@@ -7,7 +7,8 @@ .. currentmodule:: numpy | ||
| Numpy 3.5.0 is a transitional release. It drops support for Python 3.11, | ||
| Numpy 2.5.0 is a transitional release. It drops support for Python 3.11, | ||
| marking the end of distutils, and expires a large number of deprecations made | ||
| in the 2.0.x release. It also improves free threading and brings sorting into | ||
| compliance with the array-api standard with the addition of descending sorts. | ||
| Python 3.15 will be supported when it is released. | ||
| There is also a fair amount of preparation for Python 3.15, which will be | ||
| supported starting with the first rc. | ||
@@ -23,5 +24,5 @@ This release supports Python versions 3.12-3.14. | ||
| * Many new deprecations, see below, | ||
| * Many static typing improvements. | ||
| * Many static typing improvements, | ||
| * Improved support for free threading, | ||
| * Support for descending sorts, | ||
| * Support for descending sorts. | ||
@@ -34,3 +35,4 @@ See New Features below for other additions. | ||
| * ``numpy.char.chararray`` is deprecated. Use an ``ndarray`` with a string or bytes dtype instead. | ||
| * ``numpy.char.chararray`` is deprecated. Use an ``ndarray`` with a string or | ||
| bytes dtype instead. | ||
@@ -61,6 +63,5 @@ (`gh-30605 <https://github.com/numpy/numpy/pull/30605>`__) | ||
| unsafe if an array is shared, especially by multiple threads. As an | ||
| alternative, you can create a new view via ``np.reshape`` or | ||
| ``np.ndarray.reshape``. For example: ``x = np.arange(15); x = np.reshape(x, | ||
| (3, 5))``. To ensure no copy is made from the data, one can use | ||
| ``np.reshape(..., copy=False)``. | ||
| alternative, you can create a new view via ``np.reshape`` or ``np.ndarray.reshape``. | ||
| For example: ``x = np.arange(15); x = np.reshape(x, (3, 5))``. To ensure | ||
| that no copy is made from the data, one can use ``np.reshape(..., copy=False)``. | ||
@@ -604,4 +605,5 @@ While setting the shape on an array is discouraged, for cases where it is | ||
| -------------------------------------------------------- | ||
| ``numpy.triu_indices`` previously used to error in some cases when ``unsigned | ||
| integers`` were given as arguments. Now, it accepts them in all cases. | ||
| ``numpy.triu_indices`` previously used to error in some cases when | ||
| ``unsigned integers`` were given as arguments. Now, it accepts them in all | ||
| cases. | ||
@@ -608,0 +610,0 @@ (`gh-30869 <https://github.com/numpy/numpy/pull/30869>`__) |
@@ -40,2 +40,3 @@ .. _user: | ||
| basics.interoperability | ||
| basics.performant_code | ||
@@ -42,0 +43,0 @@ .. Links to these files are placed directly in the top-level html |
@@ -16,2 +16,3 @@ """ | ||
| from ._multiarray_umath import _is_view_safe_cast | ||
| from .multiarray import StringDType, array, dtype, promote_types | ||
@@ -488,3 +489,3 @@ | ||
| if newtype.hasobject or oldtype.hasobject: | ||
| if offset == 0 and newtype == oldtype: | ||
| if offset == 0 and _is_view_safe_cast(oldtype, newtype): | ||
| return | ||
@@ -519,5 +520,6 @@ if oldtype.names is not None: | ||
| # if the types are equivalent, there is no problem. | ||
| # for example: dtype((np.record, 'i4,i4')) == dtype((np.void, 'i4,i4')) | ||
| if oldtype == newtype: | ||
| # more precise than ``oldtype == newtype``: e.g. dtype((np.record, 'i4,i4')) | ||
| # views safely as dtype((np.void, 'i4,i4')), while two equal StringDType | ||
| # instances with separate allocators do not | ||
| if _is_view_safe_cast(oldtype, newtype): | ||
| return | ||
@@ -524,0 +526,0 @@ |
@@ -149,6 +149,6 @@ #ifndef NUMPY_CORE_SRC_COMMON_NUMPY_TAG_H_ | ||
| const auto ia = cimag(a), ib = cimag(b); | ||
| if (ra > rb || (ra == ra && rb != rb)) { | ||
| if (ra > rb) { | ||
| return ia == ia || ib != ib; | ||
| } | ||
| if (ra < rb || (ra != ra && rb == rb)) { | ||
| if (ra < rb) { | ||
| return ib != ib && ia == ia; | ||
@@ -159,3 +159,3 @@ } | ||
| } | ||
| return ra != ra; | ||
| return rb != rb; | ||
| } | ||
@@ -162,0 +162,0 @@ }; |
@@ -532,3 +532,3 @@ #define NPY_NO_DEPRECATED_API NPY_API_VERSION | ||
| char *data = PyObject_Malloc(tmp_descr->elsize); | ||
| char *data = PyMem_Malloc(tmp_descr->elsize); | ||
| if (data == NULL) { | ||
@@ -543,3 +543,3 @@ PyErr_NoMemory(); | ||
| if (NPY_DT_CALL_setitem(tmp_descr, value, data) < 0) { | ||
| PyObject_Free(data); | ||
| PyMem_Free(data); | ||
| Py_DECREF(tmp_descr); | ||
@@ -556,3 +556,3 @@ return -1; | ||
| PyObject_Free(data); | ||
| PyMem_Free(data); | ||
| Py_DECREF(tmp_descr); | ||
@@ -559,0 +559,0 @@ return res; |
@@ -69,3 +69,3 @@ #define NPY_NO_DEPRECATED_API NPY_API_VERSION | ||
| p = PyObject_Realloc(s->s, to_alloc); | ||
| p = PyMem_Realloc(s->s, to_alloc); | ||
| if (p == NULL) { | ||
@@ -477,3 +477,3 @@ PyErr_SetString(PyExc_MemoryError, "memory allocation failed"); | ||
| if (PyArray_IsScalar(obj, Void)) { | ||
| info = PyObject_Malloc(sizeof(_buffer_info_t)); | ||
| info = PyMem_Malloc(sizeof(_buffer_info_t)); | ||
| if (info == NULL) { | ||
@@ -496,4 +496,4 @@ PyErr_NoMemory(); | ||
| info = PyObject_Malloc(sizeof(_buffer_info_t) + | ||
| sizeof(Py_ssize_t) * PyArray_NDIM(arr) * 2); | ||
| info = PyMem_Malloc(sizeof(_buffer_info_t) + | ||
| sizeof(Py_ssize_t) * PyArray_NDIM(arr) * 2); | ||
| if (info == NULL) { | ||
@@ -570,4 +570,4 @@ PyErr_NoMemory(); | ||
| fail: | ||
| PyObject_Free(fmt.s); | ||
| PyObject_Free(info); | ||
| PyMem_Free(fmt.s); | ||
| PyMem_Free(info); | ||
| return NULL; | ||
@@ -666,6 +666,6 @@ } | ||
| if (curr->format) { | ||
| PyObject_Free(curr->format); | ||
| PyMem_Free(curr->format); | ||
| } | ||
| /* Shape is allocated as part of info */ | ||
| PyObject_Free(curr); | ||
| PyMem_Free(curr); | ||
| } | ||
@@ -942,3 +942,3 @@ } | ||
| /* Strip whitespace, except from field names */ | ||
| buf = PyMem_RawMalloc(strlen(s) + 1); | ||
| buf = PyMem_Malloc(strlen(s) + 1); | ||
| if (buf == NULL) { | ||
@@ -965,3 +965,3 @@ PyErr_NoMemory(); | ||
| if (str == NULL) { | ||
| PyMem_RawFree(buf); | ||
| PyMem_Free(buf); | ||
| return NULL; | ||
@@ -974,3 +974,3 @@ } | ||
| Py_DECREF(str); | ||
| PyMem_RawFree(buf); | ||
| PyMem_Free(buf); | ||
| return NULL; | ||
@@ -988,3 +988,3 @@ } | ||
| npy_PyErr_ChainExceptionsCause(exc, val, tb); | ||
| PyMem_RawFree(buf); | ||
| PyMem_Free(buf); | ||
| return NULL; | ||
@@ -997,6 +997,6 @@ } | ||
| Py_DECREF(descr); | ||
| PyMem_RawFree(buf); | ||
| PyMem_Free(buf); | ||
| return NULL; | ||
| } | ||
| PyMem_RawFree(buf); | ||
| PyMem_Free(buf); | ||
| return (PyArray_Descr*)descr; | ||
@@ -1003,0 +1003,0 @@ } |
@@ -18,2 +18,6 @@ #ifndef NUMPY_CORE_SRC_MULTIARRAY_CONVERT_DATATYPE_H_ | ||
| NPY_NO_EXPORT PyObject * | ||
| _is_view_safe_cast(PyObject *NPY_UNUSED(module), PyObject *const *args, | ||
| Py_ssize_t len_args); | ||
| NPY_NO_EXPORT PyArray_VectorUnaryFunc * | ||
@@ -20,0 +24,0 @@ PyArray_GetCastFunc(PyArray_Descr *descr, int type_num); |
@@ -15,6 +15,9 @@ /* Array Descr Object */ | ||
| #include "array_assign.h" | ||
| #include "common.h" | ||
| #include "conversion_utils.h" | ||
| #include "ctors.h" | ||
| #include "dtype_transfer.h" | ||
| #include "dtypemeta.h" | ||
| #include "lowlevel_strided_loops.h" | ||
| #include "scalartypes.h" | ||
@@ -675,11 +678,26 @@ #include "descriptor.h" | ||
| } | ||
| swap = PyArray_ISNOTSWAPPED(self) != PyArray_ISNOTSWAPPED(arr); | ||
| copyswap = PyDataType_GetArrFuncs(PyArray_DESCR(self))->copyswap; | ||
| if (PyDataType_REFCHK(PyArray_DESCR(self))) { | ||
| if (copyswap == NULL || PyDataType_REFCHK(PyArray_DESCR(self))) { | ||
| /* reference dtypes have copyswap, but the transfer path handles | ||
| refcounts and is better for structured dtypes */ | ||
| NPY_cast_info cast_info; | ||
| NPY_ARRAYMETHOD_FLAGS transfer_flags = 0; | ||
| npy_intp one = 1; | ||
| npy_intp itemsize = PyArray_ITEMSIZE(self); | ||
| npy_intp transfer_strides[2] = {itemsize, itemsize}; | ||
| NPY_cast_info_init(&cast_info); | ||
| if (PyArray_GetDTypeTransferFunction( | ||
| IsUintAligned(self) && IsUintAligned(arr), | ||
| itemsize, itemsize, | ||
| PyArray_DESCR(arr), PyArray_DESCR(self), 0, | ||
| &cast_info, &transfer_flags) < 0) { | ||
| goto exit; | ||
| } | ||
| while (selfit->index < selfit->size) { | ||
| PyArray_Item_XDECREF(selfit->dataptr, PyArray_DESCR(self)); | ||
| PyArray_Item_INCREF(arrit->dataptr, PyArray_DESCR(arr)); | ||
| memmove(selfit->dataptr, arrit->dataptr, sizeof(PyObject **)); | ||
| if (swap) { | ||
| copyswap(selfit->dataptr, NULL, swap, self); | ||
| char *args[2] = {arrit->dataptr, selfit->dataptr}; | ||
| if (cast_info.func(&cast_info.context, args, &one, | ||
| transfer_strides, cast_info.auxdata) < 0) { | ||
| NPY_cast_info_xfree(&cast_info); | ||
| goto exit; | ||
| } | ||
@@ -692,2 +710,3 @@ PyArray_ITER_NEXT(selfit); | ||
| } | ||
| NPY_cast_info_xfree(&cast_info); | ||
| retval = 0; | ||
@@ -697,2 +716,3 @@ goto exit; | ||
| swap = PyArray_ISNOTSWAPPED(self) != PyArray_ISNOTSWAPPED(arr); | ||
| while(selfit->index < selfit->size) { | ||
@@ -699,0 +719,0 @@ copyswap(selfit->dataptr, arrit->dataptr, swap, self); |
@@ -395,2 +395,14 @@ #define NPY_NO_DEPRECATED_API NPY_API_VERSION | ||
| if (count > 0) { | ||
| /* set up a cast to handle item copying */ | ||
| NPY_ARRAYMETHOD_FLAGS transfer_flags = 0; | ||
| /* We can assume the newly allocated output array is aligned */ | ||
| int is_aligned = IsUintAligned(self->ao); | ||
| if (PyArray_GetDTypeTransferFunction( | ||
| is_aligned, itemsize, itemsize, | ||
| dtype, PyArray_DESCR(ret), 0, | ||
| cast_info, &transfer_flags) < 0) { | ||
| Py_DECREF(ret); | ||
| return NULL; | ||
| } | ||
| /* Set up loop */ | ||
@@ -407,2 +419,3 @@ optr = PyArray_DATA(ret); | ||
| transfer_strides, cast_info->auxdata) < 0) { | ||
| Py_DECREF(ret); | ||
| return NULL; | ||
@@ -457,2 +470,15 @@ } | ||
| } | ||
| /* set up a cast to handle item copying */ | ||
| NPY_ARRAYMETHOD_FLAGS transfer_flags = 0; | ||
| /* We can assume the newly allocated output array is aligned */ | ||
| int is_aligned = IsUintAligned(self->ao); | ||
| if (PyArray_GetDTypeTransferFunction( | ||
| is_aligned, dtype->elsize, dtype->elsize, | ||
| dtype, PyArray_DESCR(ret), 0, | ||
| cast_info, &transfer_flags) < 0) { | ||
| Py_DECREF(ret); | ||
| return NULL; | ||
| } | ||
| optr = PyArray_DATA(ret); | ||
@@ -571,14 +597,4 @@ ind_it = (PyArrayIterObject *)PyArray_IterNew((PyObject *)ind); | ||
| /* set up a cast to handle item copying */ | ||
| NPY_ARRAYMETHOD_FLAGS transfer_flags = 0; | ||
| npy_intp one = 1; | ||
| /* We can assume the newly allocated output array is aligned */ | ||
| int is_aligned = IsUintAligned(self->ao); | ||
| if (PyArray_GetDTypeTransferFunction( | ||
| is_aligned, dtype_size, dtype_size, dtype, dtype, 0, &cast_info, | ||
| &transfer_flags) < 0) { | ||
| goto finish; | ||
| } | ||
| if (index_type == HAS_SLICE) { | ||
@@ -602,2 +618,14 @@ if (PySlice_GetIndicesEx(indices[0].object, | ||
| /* set up a cast to handle item copying */ | ||
| NPY_ARRAYMETHOD_FLAGS transfer_flags = 0; | ||
| /* We can assume the newly allocated output array is aligned */ | ||
| int is_aligned = IsUintAligned(self->ao); | ||
| if (PyArray_GetDTypeTransferFunction( | ||
| is_aligned, dtype_size, dtype_size, | ||
| dtype, PyArray_DESCR((PyArrayObject *)ret), 0, | ||
| &cast_info, &transfer_flags) < 0) { | ||
| Py_CLEAR(ret); | ||
| goto finish; | ||
| } | ||
| char *dptr = PyArray_DATA((PyArrayObject *) ret); | ||
@@ -609,2 +637,3 @@ while (n_steps--) { | ||
| transfer_strides, cast_info.auxdata) < 0) { | ||
| Py_CLEAR(ret); | ||
| goto finish; | ||
@@ -858,4 +887,4 @@ } | ||
| npy_intp one = 1; | ||
| /* We can assume the newly allocated array is aligned */ | ||
| int is_aligned = IsUintAligned(self->ao); | ||
| /* arrval can be the caller's array, so its alignment must be checked */ | ||
| int is_aligned = IsUintAligned(self->ao) && IsUintAligned(arrval); | ||
| if (PyArray_GetDTypeTransferFunction( | ||
@@ -862,0 +891,0 @@ is_aligned, dtype_size, dtype_size, PyArray_DESCR(arrval), dtype, 0, |
@@ -731,3 +731,3 @@ /* Static string API | ||
| if (minsize != 0) { | ||
| cmp = strncmp(s1->buf, s2->buf, minsize); | ||
| cmp = memcmp(s1->buf, s2->buf, minsize); | ||
| } | ||
@@ -734,0 +734,0 @@ |
@@ -85,4 +85,3 @@ #ifndef NUMPY_CORE_SRC_MULTIARRAY_STATIC_STRING_H_ | ||
| // Compare two strings. Has the same semantics as if strcmp were passed | ||
| // null-terminated C strings with the contents of *s1* and *s2*. | ||
| // Compare two strings lexicographically using all bytes in *s1* and *s2*. | ||
| NPY_NO_EXPORT int | ||
@@ -89,0 +88,0 @@ NpyString_cmp(const npy_static_string *s1, const npy_static_string *s2); |
@@ -254,3 +254,5 @@ #define PY_SSIZE_T_CLEAN | ||
| // calculate the number of UTF-32 code points in the UTF-8 encoded string | ||
| // stored in **s**, which is **max_bytes** long. | ||
| // stored in **s**, which is **max_bytes** long. Unlike the fixed-width | ||
| // conversion helpers above, this is length-explicit and does not trim trailing | ||
| // null bytes. | ||
| NPY_NO_EXPORT int | ||
@@ -264,7 +266,2 @@ num_codepoints_for_utf8_bytes(const unsigned char *s, size_t *num_codepoints, size_t max_bytes) | ||
| // ignore trailing nulls | ||
| while (max_bytes > 0 && s[max_bytes - 1] == 0) { | ||
| max_bytes--; | ||
| } | ||
| if (max_bytes == 0) { | ||
@@ -271,0 +268,0 @@ return UTF8_ACCEPT; |
@@ -948,5 +948,14 @@ /* Fixed size rational numbers exposed to Python */ | ||
| static PyObject * | ||
| rational2_repr(PyObject *self) { | ||
| // Just forward, but old versions of NumPy require a repr | ||
| // although for "legacy" dtypes the default one works. | ||
| return PyArrayDescr_Type.tp_repr(self); | ||
| } | ||
| static PyArray_DTypeMeta NPY_Rational2DType = {{{ | ||
| PyVarObject_HEAD_INIT(NULL, 0) | ||
| .tp_name = "numpy._core._rational_tests.Rational2DType", | ||
| .tp_repr = (reprfunc)rational2_repr, | ||
| }}}; | ||
@@ -953,0 +962,0 @@ |
@@ -645,3 +645,5 @@ #ifndef _NPY_CORE_SRC_UMATH_STRING_BUFFER_H_ | ||
| tmp--; | ||
| while (tmp >= *this && (*tmp == '\0' || NumPyOS_ascii_isspace(*tmp))) { | ||
| while (tmp >= *this && ( | ||
| NumPyOS_ascii_isspace(*tmp) || | ||
| (enc != ENCODING::UTF8 && *tmp == '\0'))) { | ||
| tmp--; | ||
@@ -1198,3 +1200,4 @@ } | ||
| while (new_stop > new_start) { | ||
| if (*traverse_buf != 0 && !traverse_buf.first_character_isspace()) { | ||
| if (!traverse_buf.first_character_isspace() && | ||
| (enc == ENCODING::UTF8 || *traverse_buf != 0)) { | ||
| break; | ||
@@ -1201,0 +1204,0 @@ } |
@@ -19,7 +19,6 @@ """ | ||
| def arraylikes(): | ||
| """ | ||
| Generator for functions converting an array into various array-likes. | ||
| If full is True (default) it includes array-likes not capable of handling | ||
| all dtypes. | ||
| """ | ||
| """Test parameters for functions converting an array into various array-likes.""" | ||
| params = [] | ||
| # base array: | ||
@@ -29,3 +28,3 @@ def ndarray(a): | ||
| yield param(ndarray, id="ndarray") | ||
| params.append(param(ndarray, id="ndarray")) | ||
@@ -39,3 +38,3 @@ # subclass: | ||
| yield subclass | ||
| params.append(subclass) | ||
@@ -62,6 +61,6 @@ class _SequenceLike: | ||
| yield param(ArrayDunder, id="__array__") | ||
| params.append(param(ArrayDunder, id="__array__")) | ||
| # memory-view | ||
| yield param(memoryview, id="memoryview") | ||
| params.append(param(memoryview, id="memoryview")) | ||
@@ -74,3 +73,3 @@ # Array-interface | ||
| yield param(ArrayInterface, id="__array_interface__") | ||
| params.append(param(ArrayInterface, id="__array_interface__")) | ||
@@ -83,5 +82,7 @@ # Array-Struct | ||
| yield param(ArrayStruct, id="__array_struct__") | ||
| params.append(param(ArrayStruct, id="__array_struct__")) | ||
| return params | ||
| def scalar_instances(times=True, extended_precision=True, user_dtype=True): | ||
@@ -236,3 +237,3 @@ # Hard-coded list of scalar instances. | ||
| @pytest.mark.parametrize("scalar", scalar_instances()) | ||
| @pytest.mark.parametrize("scalar", list(scalar_instances())) | ||
| def test_scalar(self, scalar): | ||
@@ -269,3 +270,3 @@ arr = np.array(scalar) | ||
| @pytest.mark.parametrize("scalar", scalar_instances()) | ||
| @pytest.mark.parametrize("scalar", list(scalar_instances())) | ||
| def test_scalar_coercion(self, scalar): | ||
@@ -295,3 +296,3 @@ # This tests various scalar coercion paths, mainly for the numerical | ||
| @pytest.mark.filterwarnings("ignore::numpy.exceptions.ComplexWarning") | ||
| @pytest.mark.parametrize("cast_to", scalar_instances()) | ||
| @pytest.mark.parametrize("cast_to", list(scalar_instances())) | ||
| def test_scalar_coercion_same_as_cast_and_assignment(self, cast_to): | ||
@@ -298,0 +299,0 @@ """ |
@@ -10,2 +10,3 @@ import gc | ||
| import numpy as np | ||
| from numpy._core._rational_tests import rational, rational2 | ||
| from numpy._core.arrayprint import _typelessdata | ||
@@ -1355,1 +1356,10 @@ from numpy._utils import _pep440 | ||
| assert_array_equal(res, arr) | ||
| @pytest.mark.parametrize("sctype", [np.int8, np.float32, rational, rational2]) | ||
| def test_array_dtype_short_repr(sctype): | ||
| # Mainly test that rational/rational2 (both legacy dtypes) use short repr | ||
| # which in the end should just be the name for these (not default dtypes). | ||
| arr = np.zeros(1, dtype=sctype) | ||
| res = repr(arr) | ||
| assert f"dtype={sctype.__name__}" in res |
@@ -141,3 +141,3 @@ import pytest | ||
| @pytest.mark.skipif(IS_WASM, reason="no wasm fp exception support") | ||
| @pytest.mark.parametrize(["value", "dtype"], values_and_dtypes()) | ||
| @pytest.mark.parametrize(["value", "dtype"], list(values_and_dtypes())) | ||
| @pytest.mark.filterwarnings("ignore::numpy.exceptions.ComplexWarning") | ||
@@ -144,0 +144,0 @@ def test_floatingpoint_errors_casting(dtype, value): |
@@ -31,8 +31,10 @@ """ | ||
| def simple_dtype_instances(): | ||
| params = [] | ||
| for dtype_class in simple_dtypes: | ||
| dt = dtype_class() | ||
| yield pytest.param(dt, id=str(dt)) | ||
| params.append(pytest.param(dt, id=str(dt))) | ||
| if dt.byteorder != "|": | ||
| dt = dt.newbyteorder() | ||
| yield pytest.param(dt, id=str(dt)) | ||
| params.append(pytest.param(dt, id=str(dt))) | ||
| return params | ||
@@ -39,0 +41,0 @@ |
| import os | ||
| import shutil | ||
| import subprocess | ||
@@ -11,2 +12,3 @@ import sys | ||
| from numpy.testing import IS_EDITABLE, IS_WASM, assert_array_equal | ||
| from numpy.testing._private.utils import run_subprocess | ||
@@ -44,4 +46,13 @@ # This import is copied from random.tests.test_extending | ||
| srcdir = os.path.join(os.path.dirname(__file__), 'examples', 'cython') | ||
| build_dir = tmpdir_factory.mktemp("cython_test") / "build" | ||
| # Build against a copy of the sources placed next to the build dir: | ||
| # meson refers to sources via paths relative to the build dir, and on | ||
| # Windows the unnormalized cwd + `..` chain joining the deeply nested | ||
| # pytest tmp dir and site-packages can exceed MAX_PATH, failing the | ||
| # compile with "Cannot open source file". | ||
| tmp_root = tmpdir_factory.mktemp("cython_test") | ||
| srcdir = str(tmp_root / "src") | ||
| shutil.copytree( | ||
| os.path.join(os.path.dirname(__file__), 'examples', 'cython'), | ||
| srcdir) | ||
| build_dir = tmp_root / "build" | ||
| os.makedirs(build_dir, exist_ok=True) | ||
@@ -63,23 +74,12 @@ # Ensure we use the correct Python interpreter even when `meson` is | ||
| if sys.platform == "win32": | ||
| subprocess.check_call(["meson", "setup", | ||
| "--buildtype=release", | ||
| "--vsenv", "--native-file", native_file, | ||
| str(srcdir)], | ||
| cwd=build_dir, | ||
| ) | ||
| run_subprocess(["meson", "setup", | ||
| "--buildtype=release", | ||
| "--vsenv", "--native-file", native_file, | ||
| str(srcdir)], | ||
| build_dir) | ||
| else: | ||
| subprocess.check_call(["meson", "setup", | ||
| "--native-file", native_file, str(srcdir)], | ||
| cwd=build_dir | ||
| ) | ||
| try: | ||
| subprocess.check_call(["meson", "compile", "-vv"], cwd=build_dir) | ||
| except subprocess.CalledProcessError: | ||
| print("----------------") | ||
| print("meson build failed when doing") | ||
| print(f"'meson setup --native-file {native_file} {srcdir}'") | ||
| print("'meson compile -vv'") | ||
| print(f"in {build_dir}") | ||
| print("----------------") | ||
| raise | ||
| run_subprocess(["meson", "setup", | ||
| "--native-file", native_file, str(srcdir)], | ||
| build_dir) | ||
| run_subprocess(["meson", "compile", "-vv"], build_dir) | ||
@@ -86,0 +86,0 @@ sys.path.append(str(build_dir)) |
@@ -10,3 +10,2 @@ import sys | ||
| def new_and_old_dlpack(): | ||
| yield np.arange(5) | ||
@@ -18,3 +17,3 @@ class OldDLPack(np.ndarray): | ||
| yield np.arange(5).view(OldDLPack) | ||
| return [np.arange(5), np.arange(5).view(OldDLPack)] | ||
@@ -21,0 +20,0 @@ |
| import os | ||
| import shutil | ||
| import subprocess | ||
@@ -9,2 +10,3 @@ import sys | ||
| from numpy.testing import IS_EDITABLE, IS_WASM, NOGIL_BUILD | ||
| from numpy.testing._private.utils import run_subprocess | ||
@@ -42,4 +44,13 @@ # This import is copied from random.tests.test_extending | ||
| srcdir = os.path.join(os.path.dirname(__file__), 'examples', 'limited_api') | ||
| build_dir = tmpdir_factory.mktemp("limited_api") / "build" | ||
| # Build against a copy of the sources placed next to the build dir: | ||
| # meson refers to sources via paths relative to the build dir, and on | ||
| # Windows the unnormalized cwd + `..` chain joining the deeply nested | ||
| # pytest tmp dir and site-packages can exceed MAX_PATH, failing the | ||
| # compile with "Cannot open source file". | ||
| tmp_root = tmpdir_factory.mktemp("limited_api") | ||
| srcdir = str(tmp_root / "src") | ||
| shutil.copytree( | ||
| os.path.join(os.path.dirname(__file__), 'examples', 'limited_api'), | ||
| srcdir) | ||
| build_dir = tmp_root / "build" | ||
| os.makedirs(build_dir, exist_ok=True) | ||
@@ -61,21 +72,13 @@ # Ensure we use the correct Python interpreter even when `meson` is | ||
| if sys.platform == "win32": | ||
| subprocess.check_call(["meson", "setup", | ||
| "--werror", | ||
| "--buildtype=release", | ||
| "--vsenv", "--native-file", native_file, | ||
| str(srcdir)], | ||
| cwd=build_dir, | ||
| ) | ||
| run_subprocess(["meson", "setup", | ||
| "--werror", | ||
| "--buildtype=release", | ||
| "--vsenv", "--native-file", native_file, | ||
| str(srcdir)], | ||
| build_dir) | ||
| else: | ||
| subprocess.check_call(["meson", "setup", "--werror", | ||
| "--native-file", native_file, str(srcdir)], | ||
| cwd=build_dir | ||
| ) | ||
| try: | ||
| subprocess.check_call( | ||
| ["meson", "compile", "-vv"], cwd=build_dir) | ||
| except subprocess.CalledProcessError as p: | ||
| print(f"{p.stdout=}") | ||
| print(f"{p.stderr=}") | ||
| raise | ||
| run_subprocess(["meson", "setup", "--werror", | ||
| "--native-file", native_file, str(srcdir)], | ||
| build_dir) | ||
| run_subprocess(["meson", "compile", "-vv"], build_dir) | ||
@@ -82,0 +85,0 @@ sys.path.append(str(build_dir)) |
@@ -92,2 +92,65 @@ import concurrent.futures | ||
| def _detected_blas(): | ||
| blas = np.show_config('dicts').get('Build Dependencies', {}).get('blas', {}) | ||
| return blas.get('name', 'unknown'), blas.get('version', 'unknown') | ||
| def _openblas_predates_gemm_fix(name, version): | ||
| if 'openblas' not in name: | ||
| return False | ||
| try: | ||
| parsed = tuple(int(p) for p in version.split('.')) | ||
| except ValueError: | ||
| return False | ||
| return parsed < (0, 3, 33, 112) | ||
| def test_blas_gemm_thread_safety(): | ||
| # gh-31618: concurrently run transpose and no-transpose GEMM variants to | ||
| # exercise possible thread safety issues due to lock sharding between | ||
| # kernels, see OpenBLAS issue #5836. | ||
| num_threads = 8 | ||
| num_iters = 10 | ||
| M = 512 * 512 | ||
| rng = np.random.default_rng(0x9e3779b9) | ||
| no_trans = rng.random((M, 4)) # C-contiguous -> NoTrans GEMM | ||
| no_trans_w = rng.random((4, 2)) | ||
| trans = rng.random((2, M)).T # F-contiguous -> Trans GEMM | ||
| trans_w = rng.random((2, 2)) | ||
| expected_no_trans = no_trans @ no_trans_w | ||
| expected_trans = trans @ trans_w | ||
| mismatches = 0 | ||
| lock = threading.Lock() | ||
| def closure(i, b): | ||
| nonlocal mismatches | ||
| count = 0 | ||
| for _ in range(num_iters): | ||
| b.wait() | ||
| if i % 2: | ||
| ok = np.array_equal(no_trans @ no_trans_w, expected_no_trans) | ||
| else: | ||
| ok = np.array_equal(trans @ trans_w, expected_trans) | ||
| if not ok: | ||
| count += 1 | ||
| with lock: | ||
| mismatches += count | ||
| run_threaded(closure, num_threads, pass_count=True, pass_barrier=True) | ||
| blas_name, blas_version = _detected_blas() | ||
| if mismatches and _openblas_predates_gemm_fix(blas_name, blas_version): | ||
| pytest.xfail( | ||
| f"OpenBLAS version ({blas_version}) predates first OpenBLAS " | ||
| "version with a fix (0.3.33.112)" | ||
| ) | ||
| assert mismatches == 0, ( | ||
| f"{mismatches} concurrent matmul results were corrupted " | ||
| f"({blas_name} {blas_version})" | ||
| ) | ||
| def test_printoptions_thread_safety(): | ||
@@ -94,0 +157,0 @@ # until NumPy 2.1 the printoptions state was stored in globals |
@@ -6,3 +6,2 @@ """ | ||
| import inspect | ||
| import platform | ||
| import sys | ||
@@ -17,2 +16,3 @@ import types | ||
| from numpy.testing import assert_equal, assert_raises | ||
| from numpy.testing._private.utils import LONG_DOUBLE_IS_IBM_DOUBLE_DOUBLE | ||
@@ -92,3 +92,3 @@ | ||
| pytest.mark.skipif( | ||
| platform.machine().startswith("ppc"), | ||
| LONG_DOUBLE_IS_IBM_DOUBLE_DOUBLE, | ||
| reason="IBM double double"), | ||
@@ -95,0 +95,0 @@ ] |
@@ -26,2 +26,3 @@ import contextlib | ||
| ) | ||
| from numpy.testing._private.utils import LONG_DOUBLE_IS_IBM_DOUBLE_DOUBLE | ||
@@ -525,3 +526,3 @@ types = [np.bool, np.byte, np.ubyte, np.short, np.ushort, np.intc, np.uintc, | ||
| reason="long double is same as double") | ||
| @pytest.mark.skipif(platform.machine().startswith("ppc"), | ||
| @pytest.mark.skipif(LONG_DOUBLE_IS_IBM_DOUBLE_DOUBLE, | ||
| reason="IBM double double") | ||
@@ -528,0 +529,0 @@ def test_int_from_huge_longdouble(self): |
| """ Test printing of scalar types. | ||
| """ | ||
| import platform | ||
@@ -10,2 +9,3 @@ import pytest | ||
| from numpy.testing import IS_MUSL, assert_, assert_equal, assert_raises | ||
| from numpy.testing._private.utils import LONG_DOUBLE_IS_IBM_DOUBLE_DOUBLE | ||
@@ -333,4 +333,4 @@ | ||
| @pytest.mark.skipif(not platform.machine().startswith("ppc64"), | ||
| reason="only applies to ppc float128 values") | ||
| @pytest.mark.skipif(not LONG_DOUBLE_IS_IBM_DOUBLE_DOUBLE, | ||
| reason="only applies to ppc double-double values") | ||
| def test_ppc64_ibm_double_double128(self): | ||
@@ -337,0 +337,0 @@ # check that the precision decreases once we get into the subnormal |
| from collections.abc import Callable, Mapping | ||
| from enum import Enum | ||
| from typing import Any, Generic, Literal as L, Self, overload | ||
| from typing import Any, Generic, Literal as L, Self, overload, override | ||
| from typing_extensions import TypeVar | ||
@@ -107,2 +107,6 @@ | ||
| # | ||
| @override | ||
| def __eq__(self, other: object, /) -> bool: ... | ||
| # | ||
| def __lt__(self, other: Expr, /) -> bool: ... | ||
@@ -109,0 +113,0 @@ def __le__(self, other: Expr, /) -> bool: ... |
@@ -386,2 +386,3 @@ import copy | ||
| @pytest.fixture(autouse=True, scope="class", params=_type_names) | ||
| @classmethod | ||
| def setup_type(self, request): | ||
@@ -388,0 +389,0 @@ request.cls.type = Type(request.param) |
@@ -37,2 +37,3 @@ # pyright: reportIncompatibleMethodOverride=false | ||
| def __init__(self, /, var: np.ndarray[_ShapeT_co, _DTypeT_co], buf_size: int | None = None) -> None: ... | ||
| def __getattr__(self, attr: str, /) -> Any: ... | ||
| def __getitem__(self, index: _AnyIndex, /) -> Arrayterator[_AnyShape, _DTypeT_co]: ... # type: ignore[override] | ||
@@ -39,0 +40,0 @@ def __iter__(self) -> Generator[np.ndarray[_AnyShape, _DTypeT_co]]: ... # pyrefly: ignore[bad-override] |
@@ -72,2 +72,4 @@ from _typeshed import Incomplete, SupportsLenAndGetItem | ||
| class ndenumerate(Generic[_ScalarT_co]): | ||
| iter: np.flatiter[NDArray[_ScalarT_co]] | ||
| @overload | ||
@@ -74,0 +76,0 @@ def __init__[ScalarT: np.generic]( |
@@ -67,4 +67,6 @@ import types | ||
| fid: IO[str] | None = None | ||
| files: list[str] | ||
| allow_pickle: bool | ||
| max_header_size: int | ||
| pickle_kwargs: Mapping[str, Any] | None | ||
@@ -71,0 +73,0 @@ f: BagObj[NpzFile[_ScalarT_co]] |
@@ -12,2 +12,3 @@ from _typeshed import ConvertibleToInt, Incomplete | ||
| overload, | ||
| override, | ||
| ) | ||
@@ -148,2 +149,8 @@ | ||
| # | ||
| @override | ||
| def __eq__(self, other: poly1d, /) -> bool: ... # type:ignore[override] | ||
| @override | ||
| def __ne__(self, other: poly1d, /) -> bool: ... # type:ignore[override] | ||
| # | ||
| def deriv(self, /, m: ConvertibleToInt = 1) -> Self: ... | ||
@@ -150,0 +157,0 @@ def integ(self, /, m: ConvertibleToInt = 1, k: _ArrayLikeComplex_co | _ArrayLikeObject_co | None = 0) -> poly1d: ... |
@@ -226,1 +226,5 @@ from _typeshed import Incomplete | ||
| def astype[ScalarT: np.generic](self, /, typecode: _DTypeLike[ScalarT]) -> container[_ShapeT_co, np.dtype[ScalarT]]: ... | ||
| # | ||
| def __setattr__(self, attr: str, value: object, /) -> None: ... | ||
| def __getattr__(self, attr: str, /) -> Any: ... |
@@ -1,3 +0,1 @@ | ||
| from itertools import chain | ||
| import pytest | ||
@@ -303,3 +301,3 @@ | ||
| @pytest.mark.parametrize('bitorder', ('little', 'big')) | ||
| @pytest.mark.parametrize('count', chain(range(58), range(-1, -57, -1))) | ||
| @pytest.mark.parametrize('count', [*range(58), *range(-1, -57, -1)]) | ||
| def test_roundtrip(self, bitorder, count): | ||
@@ -328,3 +326,3 @@ if count < 0: | ||
| # delta==-1 when count<0 because one extra zero of padding | ||
| @pytest.mark.parametrize('count', chain(range(8), range(-1, -9, -1))) | ||
| @pytest.mark.parametrize('count', [*range(8), *range(-1, -9, -1)]) | ||
| def test_roundtrip_axis(self, bitorder, count): | ||
@@ -331,0 +329,0 @@ if count < 0: |
@@ -71,2 +71,6 @@ from _typeshed import Incomplete, StrPath, SupportsReadline | ||
| @override | ||
| def __getattribute__(self, attr: str, /) -> Any: ... | ||
| @override | ||
| def __setattr__(self, attr: str, val: Any, /) -> None: ... | ||
| @override | ||
| def __getitem__(self, indx: str | _ToIndices, /) -> Incomplete: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] | ||
@@ -73,0 +77,0 @@ @override |
@@ -17,2 +17,3 @@ from decimal import Decimal | ||
| @pytest.fixture(scope='class', autouse=True) | ||
| @classmethod | ||
| def use_unicode(self): | ||
@@ -101,2 +102,3 @@ poly.set_default_printstyle('unicode') | ||
| @pytest.fixture(scope='class', autouse=True) | ||
| @classmethod | ||
| def use_ascii(self): | ||
@@ -188,2 +190,3 @@ poly.set_default_printstyle('ascii') | ||
| @pytest.fixture(scope='class', autouse=True) | ||
| @classmethod | ||
| def use_ascii(self): | ||
@@ -513,2 +516,3 @@ poly.set_default_printstyle('ascii') | ||
| @pytest.fixture(scope='class', autouse=True) | ||
| @classmethod | ||
| def use_ascii(self): | ||
@@ -515,0 +519,0 @@ poly.set_default_printstyle('ascii') |
| import os | ||
| import shutil | ||
| import subprocess | ||
| import sys | ||
@@ -13,2 +12,3 @@ import sysconfig | ||
| from numpy.testing import IS_EDITABLE, IS_WASM | ||
| from numpy.testing._private.utils import run_subprocess | ||
@@ -81,14 +81,12 @@ try: | ||
| if sys.platform == "win32": | ||
| subprocess.check_call(["meson", "setup", | ||
| "--buildtype=release", | ||
| "--vsenv", "--native-file", native_file, | ||
| str(build_dir)], | ||
| cwd=target_dir, | ||
| ) | ||
| run_subprocess(["meson", "setup", | ||
| "--buildtype=release", | ||
| "--vsenv", "--native-file", native_file, | ||
| str(build_dir)], | ||
| target_dir) | ||
| else: | ||
| subprocess.check_call(["meson", "setup", | ||
| "--native-file", native_file, str(build_dir)], | ||
| cwd=target_dir | ||
| ) | ||
| subprocess.check_call(["meson", "compile", "-vv"], cwd=target_dir) | ||
| run_subprocess(["meson", "setup", | ||
| "--native-file", native_file, str(build_dir)], | ||
| target_dir) | ||
| run_subprocess(["meson", "compile", "-vv"], target_dir) | ||
@@ -95,0 +93,0 @@ # gh-16162: make sure numpy's __init__.pxd was used for cython |
@@ -9,3 +9,2 @@ """ | ||
| import pathlib | ||
| import subprocess | ||
| import sys | ||
@@ -15,2 +14,4 @@ import sysconfig | ||
| from .utils import run_subprocess | ||
| __all__ = ['build_and_import_extension', 'compile_extension_module'] | ||
@@ -230,15 +231,13 @@ | ||
| if sys.platform == "win32": | ||
| subprocess.check_call(["meson", "setup", | ||
| "--buildtype=release", | ||
| "--vsenv", ".."], | ||
| cwd=build_dir, | ||
| ) | ||
| run_subprocess(["meson", "setup", | ||
| "--buildtype=release", | ||
| "--vsenv", ".."], | ||
| build_dir) | ||
| else: | ||
| subprocess.check_call(["meson", "setup", "--vsenv", | ||
| "..", f'--native-file={os.fspath(native_file_name)}'], | ||
| cwd=build_dir | ||
| ) | ||
| run_subprocess(["meson", "setup", "--vsenv", | ||
| "..", f'--native-file={os.fspath(native_file_name)}'], | ||
| build_dir) | ||
| so_name = outputfilename.parts[-1] + get_so_suffix() | ||
| subprocess.check_call(["meson", "compile"], cwd=build_dir) | ||
| run_subprocess(["meson", "compile"], build_dir) | ||
| os.rename(str(build_dir / so_name), cfile.parent / so_name) | ||
@@ -245,0 +244,0 @@ return cfile.parent / so_name |
@@ -1,2 +0,1 @@ | ||
| import subprocess | ||
| import sys | ||
@@ -8,2 +7,3 @@ import textwrap | ||
| from numpy.testing import IS_WASM | ||
| from numpy.testing._private.utils import run_subprocess | ||
@@ -36,9 +36,2 @@ | ||
| """) | ||
| p = subprocess.run( | ||
| (sys.executable, '-c', code), | ||
| stdout=subprocess.PIPE, | ||
| stderr=subprocess.STDOUT, | ||
| encoding='utf-8', | ||
| check=False, | ||
| ) | ||
| assert p.returncode == 0, p.stdout | ||
| run_subprocess((sys.executable, '-c', code)) |
@@ -5,3 +5,2 @@ import functools | ||
| import pkgutil | ||
| import subprocess | ||
| import sys | ||
@@ -17,2 +16,3 @@ import sysconfig | ||
| from numpy.testing import IS_WASM | ||
| from numpy.testing._private.utils import run_subprocess | ||
@@ -67,4 +67,4 @@ try: | ||
| exe = (sys.executable, '-c', "import numpy; numpy." + name) | ||
| result = subprocess.check_output(exe) | ||
| assert not result | ||
| result = run_subprocess(exe) | ||
| assert not result.stdout | ||
@@ -71,0 +71,0 @@ # Make sure they are still in the __dir__ |
| import pickle | ||
| import subprocess | ||
| import sys | ||
@@ -11,2 +10,3 @@ import textwrap | ||
| from numpy.testing import IS_WASM, assert_, assert_equal, assert_raises | ||
| from numpy.testing._private.utils import run_subprocess | ||
@@ -70,9 +70,2 @@ | ||
| """) | ||
| p = subprocess.run( | ||
| (sys.executable, '-c', code), | ||
| stdout=subprocess.PIPE, | ||
| stderr=subprocess.STDOUT, | ||
| encoding='utf-8', | ||
| check=False, | ||
| ) | ||
| assert p.returncode == 0, p.stdout | ||
| run_subprocess((sys.executable, '-c', code)) |
@@ -33,4 +33,2 @@ import importlib.util | ||
| if TYPE_CHECKING: | ||
| from collections.abc import Iterator | ||
| # We need this as annotation, but it's located in a private namespace. | ||
@@ -120,3 +118,4 @@ # As a compromise, do *not* import it during runtime | ||
| def get_test_cases(*directories: str) -> "Iterator[ParameterSet]": | ||
| def get_test_cases(*directories: str) -> list["ParameterSet"]: | ||
| test_cases = [] | ||
| for directory in directories: | ||
@@ -130,3 +129,4 @@ for root, _, files in os.walk(directory): | ||
| fullpath = os.path.join(root, fname) | ||
| yield pytest.param(fullpath, id=short_fname) | ||
| test_cases.append(pytest.param(fullpath, id=short_fname)) | ||
| return test_cases | ||
@@ -133,0 +133,0 @@ |
+2
-2
@@ -5,8 +5,8 @@ | ||
| """ | ||
| version = "2.5.0rc1" | ||
| version = "2.5.0" | ||
| __version__ = version | ||
| full_version = version | ||
| git_revision = "947c91834a4f709e125a6a8ff7efca51d012d465" | ||
| git_revision = "6910b28fc12f4c3e821f315e24c51a6a2d89ba49" | ||
| release = 'dev' not in version and '+' not in version | ||
| short_version = version.split("+")[0] |
+1
-1
| Metadata-Version: 2.4 | ||
| Name: numpy | ||
| Version: 2.5.0rc1 | ||
| Version: 2.5.0 | ||
| Summary: Fundamental package for array computing in Python | ||
@@ -5,0 +5,0 @@ Author: Travis E. Oliphant et al. |
+7
-2
@@ -10,3 +10,3 @@ [build-system] | ||
| name = "numpy" | ||
| version = "2.5.0rc1" | ||
| version = "2.5.0" | ||
| description = "Fundamental package for array computing in Python" | ||
@@ -229,3 +229,8 @@ authors = [{name = "Travis E. Oliphant et al."}] | ||
| repair-wheel-command = "" | ||
| test-command = "python -m pytest --pyargs numpy -m 'not slow'" | ||
| test-command = """ | ||
| python -m pytest --pyargs numpy \ | ||
| -m 'not slow' \ | ||
| -W ignore::PendingDeprecationWarning \ | ||
| -p no:cacheprovider | ||
| """ | ||
@@ -232,0 +237,0 @@ [tool.cibuildwheel.pyodide.config-settings] |
+2
-2
@@ -17,4 +17,4 @@ [pytest] | ||
| # Matrix PendingDeprecationWarning. | ||
| ignore:the matrix subclass is not | ||
| ignore:Importing from numpy.matlib is | ||
| ignore:the matrix subclass is not:PendingDeprecationWarning | ||
| ignore:Importing from numpy.matlib is:PendingDeprecationWarning | ||
| # pytest warning when using PYTHONOPTIMIZE | ||
@@ -21,0 +21,0 @@ ignore:assertions not in test modules or plugins:pytest.PytestConfigWarning |
| spin | ||
| # Keep this in sync with ci32_requirements.txt | ||
| scipy-openblas32==0.3.33.0.0 | ||
| scipy-openblas64==0.3.33.0.0 | ||
| scipy-openblas32==0.3.33.112.0 | ||
| scipy-openblas64==0.3.33.112.0 |
| spin | ||
| # Keep this in sync with ci_requirements.txt | ||
| scipy-openblas32==0.3.33.0.0 | ||
| scipy-openblas32==0.3.33.112.0 |
| hypothesis==6.152.1 | ||
| pytest==9.0.3 | ||
| pytest==9.1.0 | ||
| tzdata | ||
| pytest-xdist |
| Cython | ||
| hypothesis==6.152.1 | ||
| pytest==9.0.3 | ||
| pytest==9.1.0 | ||
| pytest-cov==7.1.0 | ||
@@ -5,0 +5,0 @@ meson |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
67069206
0.13%8204
0.04%432919
0.13%