Numpy: Problème de suivi pour la mise en œuvre de NEP-18 (__array_function__)

Créé le 25 sept. 2018  ·  54Commentaires  ·  Source: numpy/numpy

  • [x] Fonctionnalité principale pour la prise en charge des remplacements:

    • [x] Implémentation initiale en Python pur (# 12005)

    • [x] Valider les fonctions du répartiteur dans array_function_dispatch (https://github.com/numpy/numpy/pull/12099)



      • Désactiver la validation lorsque vous ne testez pas NumPy (s'il y a un impact mesurable sur les temps d'importation) (inutile)



    • [x] Ajoutez un attribut de fonction .__skip_array_function__ pour permettre de sauter __array_function__ dispatch. (https://github.com/numpy/numpy/pull/13389)

  • [x] Réimplémenter des parties de numpy/core/overrides.py en C pour la vitesse (https://github.com/numpy/numpy/issues/12028):

    • [x] get_overloaded_types_and_args

    • [x] array_function_implementation_or_override

    • [x] ndarray.__array_function__ ?

    • [x] array_function_dispatch ?

  • [x] Prise en charge des remplacements pour toutes les fonctions NumPy publiques

    • [x] numpy.core



      • [x] la partie facile (https://github.com/numpy/numpy/pull/12115)


      • [x] np.core.defchararray (# 12154)


      • [x] np.einsum et np.block (https://github.com/numpy/numpy/pull/12163)



    • [x] numpy.lib



      • [x] partie 1 (https://github.com/numpy/numpy/pull/12116)


      • [x] partie 2 (# 12119)



    • [x] numpy.fft / numpy.linalg (https://github.com/numpy/numpy/pull/12117)

    • [x] fonctions actuellement entièrement écrites en C : empty_like, concatenate, inner, where, lexsort, can_cast, min_scalar_type, result_type, dot, vdot, is_busday, busday_offset, busday_count, datetime_as_string (https://github.com/numpy/numpy/ tirer / 12175)

    • [x] linspace

    • [] [ arange? ] (https://github.com/numpy/numpy/issues/12379)

  • [x] Améliorations de la convivialité

    • [x] [meilleur message d'erreur] (https://github.com/numpy/numpy/issues/12213) pour les fonctions non implémentées (https://github.com/numpy/numpy/pull/12251)

    • [x] ndarray.__repr__ ne doit pas compter sur __array_function__ (https://github.com/numpy/numpy/pull/12212)

    • [x] stacklevel doit être augmenté de 1 pour les fonctions encapsulées, donc les retraits pointent vers le bon endroit (gh-13329)

  • [x] Corrigez tous les bogues connus / échecs de test en aval
  • [ ] Documentation

    • [x] Notes de version (# 12028)

    • [x] Documents narratifs

    • [] Les docstrings révisés pour clarifier les arguments surchargés?

__array_function__

Commentaire le plus utile

Y a-t-il une proposition pour apporter ce type de changement ou est-ce que vous dites que soutenir la répartition de np.array serait vraiment difficile et que nous ne pourrons donc jamais obtenir un soutien à 100%?

NEP 22 a quelques discussions sur les options ici. Je ne pense pas que nous puissions changer en toute sécurité la sémantique de np.asarray() pour renvoyer autre chose qu'un objet numpy.ndarray - nous aurons besoin d'un nouveau protocole pour cela.

Le problème est que np.asarray() est actuellement la manière idiomatique de transtyper en un objet tableau numpy, qui utilise peut et s'attend à correspondre exactement à numpy.ndarray , par exemple jusqu'à la disposition de la mémoire.

Il y a certainement beaucoup de cas d'utilisation où ce n'est pas le cas, mais changer ce comportement briserait beaucoup de code en aval, donc ce n'est pas un démarreur. Les projets en aval devront accepter au moins cet aspect du typage de canard en réseau.

Je comprends qu'il y a ici un compromis performance / complexité et cela pourrait être une bonne raison de ne pas les implémenter. Mais cela pourrait forcer les utilisateurs à explorer d'autres moyens pour obtenir la flexibilité qu'ils souhaitent.

Oui. NEP 18 n'est pas destiné à être une solution complète pour les alternatives NumPy, mais c'est un pas dans cette direction.

Tous les 54 commentaires

Il pourrait être bon de fusionner un préliminaire "Décorez toutes les fonctions NumPy publiques avec @array_function_dispatch" pour certaines fonctions de haut niveau et demandez aux consommateurs en aval du protocole de l'essayer

Une fois que nous fusionnons https://github.com/numpy/numpy/pull/12099, j'ai un autre PR prêt qui ajoutera des décorateurs d'expédition pour la plupart des numpy.core . Il sera assez facile de terminer les choses - celui-ci a pris moins d'une heure à assembler.

cc @ eric-wieser @mrocklin @mhvk @hameerabbasi

Voir https://github.com/shoyer/numpy/tree/array-function-easy-impl pour ma branche implémentant tous les remplacements "faciles" sur les fonctions avec des wrappers Python. Les parties restantes sont np.block , np.einsum et une poignée de fonctions multiarray écrites entièrement en C (par exemple, np.concatenate ). Je vais diviser cela en un tas de PR une fois que nous aurons terminé avec # 12099.

Notez que je n'ai pas écrit de tests pour les remplacements sur chaque fonction individuelle. J'aimerais ajouter quelques tests d'intégration lorsque nous aurons terminé (par exemple, un tableau de canard qui enregistre toutes les opérations appliquées), mais je ne pense pas qu'il serait productif d'écrire des tests de répartition pour chaque fonction individuelle. Les vérifications dans # 12099 devraient détecter les erreurs les plus courantes sur les répartiteurs, et chaque ligne de code dans les fonctions du répartiteur devrait être exécutée par les tests existants.

@shoyer - sur les tests, je suis d'accord qu'il n'est pas particulièrement utile d'écrire des tests pour chacun d'eux; au lieu de cela, dans numpy, il peut être plus judicieux de commencer à utiliser les remplacements relativement rapidement dans MaskedArray .

@mhvk me semble bien, bien que je laisse quelqu'un d'autre qui utilise / connaît MaskedArray prendre les devants à ce sujet.

Voir https://github.com/numpy/numpy/pull/12115 , https://github.com/numpy/numpy/pull/12116 , # 12119 et https://github.com/numpy/numpy/pull/ 12117 pour les PR implémentant le support __array_function__ sur les fonctions définies en Python.

@shoyer - voyant certaines implémentations, j'ai deux soucis:

  • Pour certaines fonctions, comme reshape , la fonctionnalité d'origine fournissait déjà un moyen de la remplacer, en définissant une méthode reshape . Nous abandonnons effectivement cela pour toute classe qui définit __array_function__ .
  • Pour d'autres fonctions, comme np.median , une utilisation prudente de np.asanyarray et ufuncs garantit que les sous-classes peuvent déjà les utiliser. Mais cette fonctionnalité n'est plus accessible directement.

Je pense que dans l'ensemble, ces deux choses sont probablement des avantages, car nous simplifions l'interface et pouvons rendre les implémentations optimisées pour ndarray - bien que ce dernier suggère que ndarray.__array_function__ devrait prendre en charge la conversion des listes, etc., à ndarray , afin que les implémentations puissent sauter cette partie). Pourtant, j'ai pensé le noter car cela me fait redouter de l'implémenter pour Quantity un peu plus que je ne le pensais - en termes à la fois de la quantité de travail et du succès possible en termes de performances.

bien que ce dernier suggère que ndarray .__ array_function__ devrait prendre en charge la conversion des listes, etc., en ndarray, afin que les implémentations puissent sauter cette partie).

Je ne suis pas sûr de suivre ici.

Nous sommes en effet en train de déprécier l'ancienne façon de remplacer des fonctions telles que reshape et mean , bien que l'ancienne méthode supporte toujours les implémentations incomplètes de l'API de NumPy.

Je ne suis pas sûr de suivre ici.

Je pense que le problème est que si nous implémentons __array_function__ même pour une seule fonction, les mécanismes précédents se cassent complètement et il n'y a aucun moyen de basculer. C'est pourquoi je propose de revoir ma proposition NotImplementedButCoercible .

@hameerabbasi - oui, c'est le problème. Bien que nous devions faire attention ici à la facilité avec laquelle nous nous basons sur des solutions de ruban adhésif dont nous préférerions vraiment nous débarrasser ... (c'est pourquoi j'ai écrit ci-dessus que mes "problèmes" peuvent en fait être des avantages ...) . Il y a peut-être lieu d'essayer tel quel en 1.16 et de décider ensuite, en fonction de l'expérience réelle, si nous voulons fournir une solution de secours de "ignorer mon __array_function__ pour ce cas".

Re: style du répartiteur: Mes préférences en matière de style sont basées sur des considérations de mémoire / temps d'importation et de verbosité. Tout simplement, fusionnez les répartiteurs où la signature est susceptible de rester la même. De cette façon, nous créons le moins d'objets et les hits de cache seront également plus élevés.

Cela dit, je ne suis pas trop opposé au style lambda.

Le style d'écriture des fonctions de répartiteur est maintenant apparu dans quelques PR. Il serait bon de faire un choix cohérent dans NumPy.

Nous avons quelques options:


Option 1 : écrivez un répartiteur distinct pour chaque fonction, par exemple,

def _sin_dispatcher(a):
    return (a,)


@array_function_dispatch(_sin_dispatcher)
def sin(a):
     ...


def _cos_dispatcher(a):
    return (a,)


@array_function_dispatch(_cos_dispatcher)
def cos(a):
    ...

Avantages:

  • Très lisible
  • Définitions faciles à trouver des fonctions de répartiteur
  • Effacer le message d'erreur lorsque vous fournissez les mauvais arguments, par exemple sin(x=1) -> TypeError: _sin_dispatcher() got an unexpected keyword argument 'x' .

Désavantages:

  • Beaucoup de répétition, même lorsque de nombreuses fonctions d'un module ont exactement la même signature.

Option 2 : réutiliser les fonctions du répartiteur dans un module, par exemple,

def _unary_dispatcher(a):
    return (a,)


@array_function_dispatch(_unary_dispatcher)
def sin(a):
     ...


@array_function_dispatch(_unary_dispatcher)
def cos(a):
    ...

Avantages:

  • Moins de répétition
  • Lisible

Désavantages:

  • Peut être un peu plus difficile de trouver des définitions des fonctions de répartiteur
  • Messages d'erreur légèrement moins clairs pour les mauvais arguments, par exemple sin(x=1) -> TypeError: _unary_dispatcher() got an unexpected keyword argument 'x'

Option 3 : Utilisez les fonctions lambda lorsque la définition du répartiteur tient sur une seule ligne, par exemple,

# inline style (shorter)
@array_function_dispatch(lambda a: (a,))
def sin(a):
     ...


@array_function_dispatch(lambda a, n=None, axis=None, norm=None: (a,))
def fft(a, n=None, axis=-1, norm=None):
     ...
# multiline style (more readable?)
@array_function_dispatch(
    lambda a: (a,)
)
def sin(a):
     ...


@array_function_dispatch(
    lambda a, n=None, axis=None, norm=None: (a,)
)
def fft(a, n=None, axis=-1, norm=None):
     ...

Avantages:

  • Pas besoin de chercher les définitions de répartiteur, elles sont là.
  • Moins de nombre de caractères et de lignes de codes.
  • Semble très bien pour les cas courts (par exemple, un argument), en particulier lorsque le lambda est plus court que le nom de la fonction.

Désavantages:

  • Code plus répété que l'option 2.
  • Semble assez encombré s'il y a plus de quelques arguments
  • A également des messages d'erreur moins clairs ( TypeError: <lambda>() got an unexpected keyword argument 'x' )

@shoyer : modifié pour ajouter l'espacement PEP8 sur deux lignes pour rendre l'aspect "lignes de code" plus réaliste

Notez que les problèmes de message d'erreur peuvent être résolus en reconstruisant l'objet de code , bien que cela entraîne un certain coût en temps d'importation. Cela vaut peut-être la peine d'enquêter et de sortir le thon de

Oui, le module décorateur pourrait également être utilisé pour générer la définition de fonction (il utilise une approche légèrement différente pour la génération de code, un peu plus comme namedtuple en ce qu'il utilise exec() ).

Tant que l'erreur n'est pas résolue, je pense que nous devons nous en tenir aux options avec un répartiteur qui a un nom clair. Je vais légèrement regrouper les répartiteurs (2) pour des raisons de mémoire, mais je garderais alors le message d'erreur à l'esprit, donc je suggérerais d'appeler le répartiteur quelque chose comme _dispatch_on_x .

Mais si nous pouvons changer l'erreur, les choses changent. Par exemple, cela peut être aussi simple que d'attraper des exceptions, de remplacer <lambda> par le nom de la fonction dans le texte de l'exception, puis de le relancer. (Ou est-ce que cette chaîne est de nos jours?)

Je suis d'accord que le message d'erreur doit être clair, idéalement ne devrait pas changer du tout.

OK, pour l'instant, je pense qu'il est préférable de ne pas utiliser lambda , sauf si nous obtenons une sorte de génération de code qui fonctionne.

https://github.com/numpy/numpy/pull/12175 ajoute un brouillon de ce à quoi pourraient ressembler les remplacements pour les fonctions multiarray (écrites en C) si nous adoptons l'approche wrapper Python.

@mattip où en sommes-nous sur l'implémentation de matmul comme ufunc? Une fois que nous avons terminé tous ces remplacements __array_function__ , je pense que c'est la dernière chose dont nous avons besoin pour rendre l'API publique de NumPy entièrement surchargeable. Ce serait bien d'avoir tout prêt pour NumPy 1.16!

Le PR # 11175, qui met en œuvre le NEP 20, progresse lentement. C'est un bloqueur pour PR # 11133, qui a le code de boucle matmul. Celui-ci doit encore être mis à jour puis vérifié via des benchmarks que le nouveau code n'est pas plus lent que l'ancien.

J'ai quatre PR à examiner qui devraient compléter l'ensemble complet des dérogations. Les révisions / approbations / fusions finales seraient appréciées afin que nous puissions commencer à tester __array_function__ sérieusement! https://github.com/numpy/numpy/pull/12154 , https://github.com/numpy/numpy/pull/12163 , https://github.com/numpy/numpy/pull/12119 , https: //github.com/numpy/numpy/pull/12175

L'ajout de remplacements à np.core provoqué l'échec de quelques tests pandas (https://github.com/pandas-dev/pandas/issues/23172). Nous ne sommes pas encore tout à fait sûr de ce qui se passe, mais nous devons absolument le comprendre avant de sortir.

Voir https://github.com/numpy/numpy/issues/12225 pour ma meilleure estimation de la raison pour laquelle cela provoque des échecs de test dans dask / pandas.

Quelques repères des temps d'import (sur mon macbook pro avec un disque SSD):

  • NumPy 1.15.2: 152,451 ms
  • Maître NumPy: 156,5745 ms
  • Utilisation de decorator.decorate (# 12226): 183,694 ms

Mon script de référence

import numpy as np
import subprocess

times = []
for _ in range(100):
    result = subprocess.run("python -X importtime -c 'import numpy'",
                            shell=True, capture_output=True)
    last_line = result.stderr.rstrip().split(b'\n')[-1]
    time = float(last_line.decode('ascii')[-15:-7].strip().rstrip())
    times.append(time)

print(np.median(times) / 1e3)

Une idée de l'utilisation de la mémoire (avant / après)? C'est également très utile, en particulier pour les applications IoT.

Savez-vous comment mesurer de manière fiable l'utilisation de la mémoire pour un module?
Le sam 20 octobre 2018 à 6 h 56 Hameer Abbasi [email protected]
a écrit:

Une idée de l'utilisation de la mémoire (avant / après)? C'est un peu utile car
bien, en particulier pour les applications IoT.

-
Vous recevez cela parce que vous avez été mentionné.
Répondez directement à cet e-mail, affichez-le sur GitHub
https://github.com/numpy/numpy/issues/12028#issuecomment-431584123 , ou muet
le fil
https://github.com/notifications/unsubscribe-auth/ABKS1k_IkrJ2YmYReaDrnkNvcH2X0-ZCks5umyuogaJpZM4W3kSC
.

Je pense qu'écrire un script contenant import numpy as np , ajouter une instruction de sommeil et suivre la mémoire du processus devrait suffire. https://superuser.com/questions/581108/how-can-i-track-and-log-cpu-and-memory-usage-on-a-mac

Tous les autres développeurs de base veulent jeter un coup d'œil rapide (vraiment, cela ne comprend que deux fonctions!) À https://github.com/numpy/numpy/pull/12163? C'est le dernier PR ajoutant array_function_dispatch aux fonctions numpy internes.

Pour référence, voici la différence de performances que je vois lors de la désactivation de __array_function__ :

       before           after         ratio
     [45718fd7]       [4e5aa2cd]
     <master>         <disable-array-function>
+        72.5±2ms         132±20ms     1.82  bench_io.LoadtxtCSVdtypes.time_loadtxt_dtypes_csv('complex128', 10000)
-        44.9±2μs       40.8±0.6μs     0.91  bench_ma.Concatenate.time_it('ndarray', 2)
-      15.3±0.3μs       13.3±0.7μs     0.87  bench_core.CountNonzero.time_count_nonzero_multi_axis(2, 100, <type 'object'>)
-        38.4±1μs         32.7±2μs     0.85  bench_linalg.Linalg.time_op('norm', 'longfloat')
-        68.7±3μs         56.5±3μs     0.82  bench_linalg.Linalg.time_op('norm', 'complex256')
-        80.6±4μs         65.9±1μs     0.82  bench_function_base.Median.time_even
-        82.4±2μs         66.8±3μs     0.81  bench_shape_base.Block.time_no_lists(100)
-        73.5±3μs         59.3±3μs     0.81  bench_function_base.Median.time_even_inplace
-      15.2±0.3μs       12.2±0.6μs     0.80  bench_core.CountNonzero.time_count_nonzero_multi_axis(3, 100, <type 'str'>)
-      2.20±0.1ms      1.76±0.04ms     0.80  bench_shape_base.Block2D.time_block2d((1024, 1024), 'uint64', (4, 4))
-        388±20μs         310±10μs     0.80  bench_lib.Pad.time_pad((10, 10, 10), 3, 'linear_ramp')
-        659±20μs         524±20μs     0.80  bench_linalg.Linalg.time_op('det', 'float32')
-      22.9±0.7μs       18.2±0.8μs     0.79  bench_function_base.Where.time_1
-        980±50μs         775±20μs     0.79  bench_shape_base.Block2D.time_block2d((1024, 1024), 'uint32', (4, 4))
-        36.6±1μs         29.0±1μs     0.79  bench_ma.Concatenate.time_it('unmasked', 2)
-      16.4±0.7μs       12.9±0.6μs     0.79  bench_core.CountNonzero.time_count_nonzero_axis(3, 100, <type 'str'>)
-      16.4±0.5μs       12.9±0.4μs     0.79  bench_core.CountNonzero.time_count_nonzero_axis(2, 100, <type 'object'>)
-         141±5μs          110±4μs     0.78  bench_lib.Pad.time_pad((10, 100), (0, 5), 'linear_ramp')
-      18.0±0.6μs       14.1±0.6μs     0.78  bench_core.CountNonzero.time_count_nonzero_axis(3, 100, <type 'object'>)
-      11.9±0.6μs       9.28±0.5μs     0.78  bench_core.CountNonzero.time_count_nonzero_axis(1, 100, <type 'int'>)
-        54.6±3μs         42.4±2μs     0.78  bench_function_base.Median.time_odd_small
-        317±10μs          246±7μs     0.78  bench_lib.Pad.time_pad((10, 10, 10), 1, 'linear_ramp')
-      13.8±0.5μs       10.7±0.7μs     0.77  bench_reduce.MinMax.time_min(<type 'numpy.float64'>)
-        73.3±6μs         56.6±4μs     0.77  bench_lib.Pad.time_pad((1000,), (0, 5), 'mean')
-      14.7±0.7μs       11.4±0.3μs     0.77  bench_core.CountNonzero.time_count_nonzero_axis(2, 100, <type 'str'>)
-        21.5±2μs       16.5±0.6μs     0.77  bench_reduce.MinMax.time_min(<type 'numpy.int64'>)
-         117±4μs         89.2±3μs     0.76  bench_lib.Pad.time_pad((1000,), 3, 'linear_ramp')
-        43.7±1μs         33.4±1μs     0.76  bench_linalg.Linalg.time_op('norm', 'complex128')
-      12.6±0.6μs       9.55±0.2μs     0.76  bench_core.CountNonzero.time_count_nonzero_multi_axis(2, 100, <type 'int'>)
-        636±20μs         482±20μs     0.76  bench_ma.MA.time_masked_array_l100
-        86.6±4μs         65.6±4μs     0.76  bench_lib.Pad.time_pad((1000,), (0, 5), 'linear_ramp')
-         120±4μs         90.4±2μs     0.75  bench_lib.Pad.time_pad((1000,), 1, 'linear_ramp')
-         160±5μs          119±8μs     0.74  bench_ma.Concatenate.time_it('ndarray+masked', 100)
-      14.4±0.6μs       10.7±0.3μs     0.74  bench_core.CountNonzero.time_count_nonzero_multi_axis(1, 100, <type 'str'>)
-      15.7±0.4μs       11.7±0.6μs     0.74  bench_core.CountNonzero.time_count_nonzero_multi_axis(2, 100, <type 'str'>)
-        21.8±2μs       16.1±0.7μs     0.74  bench_reduce.MinMax.time_max(<type 'numpy.int64'>)
-      11.9±0.6μs       8.79±0.3μs     0.74  bench_core.CountNonzero.time_count_nonzero_axis(2, 100, <type 'bool'>)
-        53.8±3μs         39.4±2μs     0.73  bench_function_base.Median.time_even_small
-        106±20μs         76.7±4μs     0.73  bench_function_base.Select.time_select
-        168±10μs          122±4μs     0.72  bench_shape_base.Block2D.time_block2d((512, 512), 'uint32', (2, 2))
-      12.5±0.5μs       8.96±0.4μs     0.72  bench_core.CountNonzero.time_count_nonzero_multi_axis(1, 100, <type 'int'>)
-        162±10μs          115±5μs     0.71  bench_function_base.Percentile.time_percentile
-        12.9±1μs       9.12±0.4μs     0.71  bench_random.Random.time_rng('normal')
-      9.71±0.4μs       6.88±0.3μs     0.71  bench_core.CorrConv.time_convolve(1000, 10, 'full')
-      15.1±0.8μs       10.7±0.4μs     0.71  bench_reduce.MinMax.time_max(<type 'numpy.float64'>)
-         153±9μs          108±7μs     0.71  bench_shape_base.Block2D.time_block2d((1024, 1024), 'uint8', (2, 2))
-         109±5μs         76.9±5μs     0.71  bench_ma.Concatenate.time_it('ndarray+masked', 2)
-        34.3±1μs       24.2±0.6μs     0.71  bench_linalg.Linalg.time_op('norm', 'complex64')
-      9.80±0.2μs       6.84±0.5μs     0.70  bench_core.CorrConv.time_convolve(1000, 10, 'same')
-        27.4±6μs         19.1±2μs     0.70  bench_core.CountNonzero.time_count_nonzero_axis(1, 10000, <type 'bool'>)
-      9.35±0.4μs       6.50±0.3μs     0.70  bench_core.CorrConv.time_convolve(50, 100, 'full')
-        65.2±4μs         45.2±1μs     0.69  bench_shape_base.Block.time_block_simple_row_wise(100)
-        12.9±1μs       8.89±0.3μs     0.69  bench_core.CountNonzero.time_count_nonzero_axis(3, 100, <type 'bool'>)
-        19.6±3μs       13.5±0.4μs     0.69  bench_core.CountNonzero.time_count_nonzero_multi_axis(3, 100, <type 'object'>)
-        75.6±2μs         52.1±3μs     0.69  bench_lib.Pad.time_pad((10, 10, 10), (0, 5), 'reflect')
-        12.4±1μs       8.51±0.4μs     0.69  bench_core.CountNonzero.time_count_nonzero_multi_axis(3, 100, <type 'bool'>)
-        172±30μs          117±4μs     0.68  bench_ma.Concatenate.time_it('unmasked+masked', 100)
-      23.1±0.5μs       15.8±0.9μs     0.68  bench_linalg.Linalg.time_op('norm', 'int16')
-      8.18±0.9μs       5.57±0.1μs     0.68  bench_core.CorrConv.time_correlate(1000, 10, 'full')
-         153±5μs          103±3μs     0.68  bench_function_base.Percentile.time_quartile
-       758±100μs         512±20μs     0.68  bench_linalg.Linalg.time_op('det', 'int16')
-        55.4±6μs         37.4±1μs     0.68  bench_ma.Concatenate.time_it('masked', 2)
-        234±30μs          157±5μs     0.67  bench_shape_base.Block.time_nested(100)
-         103±4μs         69.3±3μs     0.67  bench_linalg.Eindot.time_dot_d_dot_b_c
-      19.2±0.4μs       12.9±0.6μs     0.67  bench_core.Core.time_tril_l10x10
-         122±7μs         81.7±4μs     0.67  bench_lib.Pad.time_pad((10, 10, 10), 3, 'edge')
-        22.9±1μs       15.3±0.5μs     0.67  bench_linalg.Linalg.time_op('norm', 'int32')
-        16.6±2μs       11.0±0.3μs     0.66  bench_core.CountNonzero.time_count_nonzero_multi_axis(1, 100, <type 'object'>)
-      9.98±0.3μs       6.58±0.1μs     0.66  bench_core.CorrConv.time_convolve(1000, 10, 'valid')
-         118±6μs         77.9±4μs     0.66  bench_shape_base.Block2D.time_block2d((512, 512), 'uint16', (2, 2))
-        212±50μs          140±8μs     0.66  bench_lib.Pad.time_pad((10, 10, 10), (0, 5), 'mean')
-      21.9±0.7μs       14.4±0.5μs     0.66  bench_linalg.Linalg.time_op('norm', 'int64')
-         131±5μs         85.9±5μs     0.65  bench_lib.Pad.time_pad((10, 10, 10), 3, 'constant')
-        56.8±2μs         37.0±3μs     0.65  bench_lib.Pad.time_pad((1000,), (0, 5), 'constant')
-        58.9±3μs         38.1±1μs     0.65  bench_lib.Pad.time_pad((10, 100), (0, 5), 'reflect')
-        72.1±2μs         46.5±3μs     0.64  bench_lib.Pad.time_pad((10, 100), (0, 5), 'constant')
-      8.66±0.3μs       5.58±0.2μs     0.64  bench_core.CorrConv.time_correlate(50, 100, 'full')
-        300±30μs         193±10μs     0.64  bench_shape_base.Block2D.time_block2d((1024, 1024), 'uint8', (4, 4))
-        15.9±5μs       10.2±0.3μs     0.64  bench_core.CountNonzero.time_count_nonzero_axis(3, 100, <type 'int'>)
-      13.7±0.5μs       8.80±0.1μs     0.64  bench_random.Random.time_rng('uniform')
-      8.60±0.5μs       5.50±0.2μs     0.64  bench_core.CorrConv.time_correlate(1000, 10, 'same')
-        44.7±2μs       28.5±0.7μs     0.64  bench_lib.Pad.time_pad((1000,), 1, 'reflect')
-        72.7±3μs         46.2±2μs     0.64  bench_lib.Pad.time_pad((10, 10, 10), 3, 'wrap')
-        567±50μs         360±40μs     0.63  bench_shape_base.Block2D.time_block2d((512, 512), 'uint64', (2, 2))
-        58.0±3μs         36.7±2μs     0.63  bench_lib.Pad.time_pad((10, 100), 3, 'reflect')
-        219±30μs          138±7μs     0.63  bench_lib.Pad.time_pad((10, 100), 1, 'mean')
-        261±60μs         164±10μs     0.63  bench_lib.Pad.time_pad((10, 100), 1, 'linear_ramp')
-       825±100μs         519±30μs     0.63  bench_shape_base.Block2D.time_block2d((512, 512), 'uint64', (4, 4))
-         121±5μs         75.7±2μs     0.63  bench_lib.Pad.time_pad((10, 10, 10), 1, 'constant')
-      8.16±0.2μs       5.08±0.4μs     0.62  bench_core.CorrConv.time_convolve(50, 100, 'same')
-        66.6±3μs         41.3±2μs     0.62  bench_lib.Pad.time_pad((1000,), 3, 'constant')
-        53.1±3μs       32.9±0.8μs     0.62  bench_lib.Pad.time_pad((10, 100), 3, 'wrap')
-        285±60μs         177±10μs     0.62  bench_lib.Pad.time_pad((10, 10, 10), (0, 5), 'linear_ramp')
-      8.30±0.9μs       5.14±0.1μs     0.62  bench_core.CorrConv.time_correlate(1000, 10, 'valid')
-         115±3μs         71.2±3μs     0.62  bench_shape_base.Block2D.time_block2d((256, 256), 'uint64', (2, 2))
-      19.1±0.5μs       11.8±0.6μs     0.62  bench_linalg.Linalg.time_op('norm', 'float64')
-        95.3±5μs         58.6±2μs     0.62  bench_lib.Pad.time_pad((10, 100), 1, 'constant')
-        44.6±1μs       27.2±0.9μs     0.61  bench_lib.Pad.time_pad((1000,), (0, 5), 'edge')
-        447±20μs         270±10μs     0.61  bench_shape_base.Block2D.time_block2d((1024, 1024), 'uint16', (4, 4))
-        53.9±2μs         32.6±2μs     0.60  bench_lib.Pad.time_pad((10, 100), 1, 'wrap')
-        11.6±1μs       6.97±0.4μs     0.60  bench_reduce.MinMax.time_max(<type 'numpy.float32'>)
-        95.9±5μs         57.7±2μs     0.60  bench_lib.Pad.time_pad((10, 100), 3, 'constant')
-        47.2±2μs         28.2±2μs     0.60  bench_lib.Pad.time_pad((1000,), (0, 5), 'reflect')
-      5.51±0.2μs      3.27±0.07μs     0.59  bench_core.CountNonzero.time_count_nonzero(3, 100, <type 'object'>)
-        74.3±3μs         44.0±2μs     0.59  bench_lib.Pad.time_pad((10, 10, 10), (0, 5), 'wrap')
-        76.2±3μs       45.0±0.8μs     0.59  bench_lib.Pad.time_pad((10, 10, 10), 1, 'reflect')
-        57.1±1μs         33.5±2μs     0.59  bench_lib.Pad.time_pad((10, 100), (0, 5), 'wrap')
-        52.0±2μs         30.4±1μs     0.58  bench_lib.Pad.time_pad((1000,), 1, 'edge')
-        42.6±2μs       24.9±0.9μs     0.58  bench_lib.Pad.time_pad((1000,), 3, 'wrap')
-        15.0±3μs       8.73±0.3μs     0.58  bench_core.CountNonzero.time_count_nonzero_multi_axis(1, 100, <type 'bool'>)
-        16.0±3μs       9.29±0.3μs     0.58  bench_core.CountNonzero.time_count_nonzero_multi_axis(3, 100, <type 'int'>)
-        53.1±1μs         30.9±2μs     0.58  bench_lib.Pad.time_pad((1000,), 3, 'edge')
-        88.0±8μs         51.1±3μs     0.58  bench_lib.Pad.time_pad((10, 10, 10), 3, 'reflect')
-        44.6±2μs         25.9±1μs     0.58  bench_lib.Pad.time_pad((1000,), (0, 5), 'wrap')
-        90.3±5μs         51.9±1μs     0.57  bench_shape_base.Block2D.time_block2d((512, 512), 'uint8', (2, 2))
-      15.6±0.5μs       8.93±0.3μs     0.57  bench_linalg.Linalg.time_op('norm', 'float32')
-         102±6μs       58.3±0.9μs     0.57  bench_lib.Pad.time_pad((10, 10, 10), 1, 'edge')
-        80.1±4μs         45.6±3μs     0.57  bench_lib.Pad.time_pad((10, 100), 3, 'edge')
-        44.2±2μs         24.9±1μs     0.56  bench_lib.Pad.time_pad((1000,), 1, 'wrap')
-        71.6±8μs         39.5±1μs     0.55  bench_lib.Pad.time_pad((10, 10, 10), 1, 'wrap')
-       81.7±10μs         44.8±2μs     0.55  bench_lib.Pad.time_pad((10, 100), 1, 'edge')
-        420±90μs         230±10μs     0.55  bench_shape_base.Block.time_3d(10, 'block')
-        114±20μs         62.3±2μs     0.55  bench_lib.Pad.time_pad((10, 10, 10), (0, 5), 'constant')
-      5.76±0.1μs      3.13±0.08μs     0.54  bench_core.CorrConv.time_convolve(50, 10, 'same')
-      5.30±0.1μs      2.84±0.08μs     0.54  bench_core.CorrConv.time_correlate(50, 100, 'valid')
-        92.5±4μs         49.3±1μs     0.53  bench_shape_base.Block2D.time_block2d((256, 256), 'uint32', (2, 2))
-        13.5±3μs       7.07±0.2μs     0.52  bench_reduce.MinMax.time_min(<type 'numpy.float32'>)
-        7.66±1μs       3.88±0.2μs     0.51  bench_core.CorrConv.time_convolve(50, 100, 'valid')
-        29.0±3μs       14.5±0.8μs     0.50  bench_shape_base.Block.time_no_lists(10)
-      6.62±0.3μs       3.30±0.2μs     0.50  bench_core.CorrConv.time_convolve(1000, 1000, 'valid')
-        74.2±7μs       36.2±0.9μs     0.49  bench_shape_base.Block2D.time_block2d((256, 256), 'uint16', (2, 2))
-      5.55±0.3μs       2.70±0.2μs     0.49  bench_core.CorrConv.time_convolve(50, 10, 'valid')
-       73.9±20μs         35.8±2μs     0.48  bench_lib.Pad.time_pad((10, 100), 1, 'reflect')
-        224±20μs          107±7μs     0.48  bench_shape_base.Block2D.time_block2d((256, 256), 'uint64', (4, 4))
-      3.87±0.1μs      1.83±0.06μs     0.47  bench_core.CountNonzero.time_count_nonzero(2, 100, <type 'str'>)
-        109±30μs         51.5±3μs     0.47  bench_lib.Pad.time_pad((10, 10, 10), (0, 5), 'edge')
-        240±20μs          112±4μs     0.47  bench_shape_base.Block2D.time_block2d((512, 512), 'uint16', (4, 4))
-        337±40μs          158±7μs     0.47  bench_shape_base.Block2D.time_block2d((512, 512), 'uint32', (4, 4))
-         188±8μs         88.0±2μs     0.47  bench_shape_base.Block2D.time_block2d((512, 512), 'uint8', (4, 4))
-      4.39±0.2μs      2.04±0.09μs     0.47  bench_core.CountNonzero.time_count_nonzero(3, 10000, <type 'bool'>)
-        73.2±4μs       33.9±0.5μs     0.46  bench_shape_base.Block2D.time_block2d((128, 128), 'uint64', (2, 2))
-        5.48±1μs       2.44±0.1μs     0.45  bench_core.CountNonzero.time_count_nonzero(2, 100, <type 'object'>)
-      4.46±0.1μs      1.97±0.08μs     0.44  bench_core.CorrConv.time_correlate(50, 10, 'full')
-        30.4±9μs       13.3±0.3μs     0.44  bench_shape_base.Block.time_no_lists(1)
-      7.05±0.2μs      3.05±0.06μs     0.43  bench_reduce.SmallReduction.time_small
-        7.35±1μs       3.12±0.2μs     0.42  bench_core.CorrConv.time_convolve(50, 10, 'full')
-      4.36±0.1μs      1.84±0.07μs     0.42  bench_core.CorrConv.time_correlate(50, 10, 'same')
-      3.51±0.2μs      1.46±0.05μs     0.42  bench_core.CountNonzero.time_count_nonzero(1, 100, <type 'object'>)
-     4.03±0.05μs       1.66±0.1μs     0.41  bench_core.CorrConv.time_correlate(1000, 1000, 'valid')
-        199±10μs         80.1±3μs     0.40  bench_shape_base.Block2D.time_block2d((256, 256), 'uint32', (4, 4))
-      3.98±0.2μs      1.60±0.08μs     0.40  bench_core.CountNonzero.time_count_nonzero(2, 10000, <type 'bool'>)
-        61.8±2μs         24.8±1μs     0.40  bench_shape_base.Block2D.time_block2d((256, 256), 'uint8', (2, 2))
-      4.13±0.1μs      1.62±0.05μs     0.39  bench_core.CorrConv.time_correlate(50, 10, 'valid')
-        61.6±2μs         23.9±1μs     0.39  bench_shape_base.Block2D.time_block2d((128, 128), 'uint32', (2, 2))
-        184±10μs         70.5±3μs     0.38  bench_shape_base.Block2D.time_block2d((256, 256), 'uint16', (4, 4))
-        56.1±4μs       21.0±0.9μs     0.38  bench_shape_base.Block2D.time_block2d((64, 64), 'uint64', (2, 2))
-        40.0±2μs       15.0±0.6μs     0.37  bench_shape_base.Block.time_block_simple_column_wise(10)
-         121±2μs         45.1±2μs     0.37  bench_shape_base.Block.time_nested(1)
-         179±4μs         66.1±4μs     0.37  bench_shape_base.Block2D.time_block2d((128, 128), 'uint64', (4, 4))
-        59.8±2μs         22.0±1μs     0.37  bench_shape_base.Block2D.time_block2d((128, 128), 'uint16', (2, 2))
-     3.19±0.05μs      1.17±0.02μs     0.37  bench_core.CountNonzero.time_count_nonzero(1, 100, <type 'str'>)
-        54.0±3μs         19.7±1μs     0.37  bench_shape_base.Block2D.time_block2d((32, 32), 'uint64', (2, 2))
-        56.9±1μs       20.7±0.7μs     0.36  bench_shape_base.Block2D.time_block2d((64, 64), 'uint32', (2, 2))
-      3.14±0.1μs      1.14±0.04μs     0.36  bench_core.CountNonzero.time_count_nonzero(1, 10000, <type 'bool'>)
-        92.7±2μs         33.7±2μs     0.36  bench_shape_base.Block.time_block_complicated(1)
-         104±4μs         37.8±1μs     0.36  bench_shape_base.Block.time_block_complicated(10)
-         128±5μs         45.5±2μs     0.36  bench_shape_base.Block.time_nested(10)
-       196±100μs         69.4±3μs     0.35  bench_ma.Concatenate.time_it('unmasked+masked', 2)
-         153±5μs         53.9±2μs     0.35  bench_shape_base.Block2D.time_block2d((128, 128), 'uint16', (4, 4))
-        39.4±2μs       13.8±0.5μs     0.35  bench_shape_base.Block.time_block_simple_column_wise(1)
-        53.5±2μs         18.7±1μs     0.35  bench_shape_base.Block2D.time_block2d((32, 32), 'uint8', (2, 2))
-        55.2±2μs       19.3±0.6μs     0.35  bench_shape_base.Block2D.time_block2d((32, 32), 'uint16', (2, 2))
-        16.9±1μs       5.89±0.5μs     0.35  bench_core.Core.time_dstack_l
-        60.6±3μs       21.1±0.6μs     0.35  bench_shape_base.Block2D.time_block2d((128, 128), 'uint8', (2, 2))
-      25.5±0.2μs       8.88±0.3μs     0.35  bench_shape_base.Block.time_block_simple_row_wise(10)
-        54.6±3μs       19.0±0.6μs     0.35  bench_shape_base.Block2D.time_block2d((16, 16), 'uint64', (2, 2))
-        52.6±2μs       18.2±0.7μs     0.35  bench_shape_base.Block2D.time_block2d((16, 16), 'uint16', (2, 2))
-        6.57±2μs      2.25±0.08μs     0.34  bench_core.CountNonzero.time_count_nonzero(3, 100, <type 'str'>)
-        24.3±1μs       8.30±0.6μs     0.34  bench_shape_base.Block.time_block_simple_row_wise(1)
-         148±3μs         50.0±3μs     0.34  bench_shape_base.Block2D.time_block2d((16, 16), 'uint32', (4, 4))
-         171±8μs         57.9±4μs     0.34  bench_shape_base.Block2D.time_block2d((256, 256), 'uint8', (4, 4))
-         159±5μs         53.8±1μs     0.34  bench_shape_base.Block2D.time_block2d((64, 64), 'uint64', (4, 4))
-        171±20μs         57.7±2μs     0.34  bench_shape_base.Block2D.time_block2d((128, 128), 'uint32', (4, 4))
-      3.15±0.3μs      1.06±0.03μs     0.34  bench_core.CountNonzero.time_count_nonzero(3, 100, <type 'int'>)
-        55.7±5μs       18.7±0.2μs     0.34  bench_shape_base.Block2D.time_block2d((16, 16), 'uint8', (2, 2))
-         158±7μs         52.6±3μs     0.33  bench_shape_base.Block2D.time_block2d((128, 128), 'uint8', (4, 4))
-         153±4μs         50.7±1μs     0.33  bench_shape_base.Block2D.time_block2d((32, 32), 'uint64', (4, 4))
-         152±7μs         50.3±1μs     0.33  bench_shape_base.Block2D.time_block2d((16, 16), 'uint8', (4, 4))
-        53.6±3μs       17.7±0.4μs     0.33  bench_shape_base.Block2D.time_block2d((16, 16), 'uint32', (2, 2))
-         156±4μs         51.4±3μs     0.33  bench_shape_base.Block2D.time_block2d((64, 64), 'uint8', (4, 4))
-         148±3μs         48.2±2μs     0.33  bench_shape_base.Block2D.time_block2d((16, 16), 'uint16', (4, 4))
-        160±10μs         52.0±1μs     0.33  bench_shape_base.Block2D.time_block2d((64, 64), 'uint32', (4, 4))
-         159±8μs         51.4±3μs     0.32  bench_shape_base.Block2D.time_block2d((64, 64), 'uint16', (4, 4))
-        59.8±3μs         19.3±1μs     0.32  bench_shape_base.Block2D.time_block2d((32, 32), 'uint32', (2, 2))
-         153±4μs         49.4±2μs     0.32  bench_shape_base.Block2D.time_block2d((32, 32), 'uint32', (4, 4))
-      15.6±0.6μs       5.03±0.3μs     0.32  bench_core.Core.time_vstack_l
-         154±7μs         49.7±2μs     0.32  bench_shape_base.Block2D.time_block2d((32, 32), 'uint8', (4, 4))
-        59.6±6μs       19.1±0.8μs     0.32  bench_shape_base.Block2D.time_block2d((64, 64), 'uint8', (2, 2))
-      3.03±0.4μs         969±30ns     0.32  bench_core.CountNonzero.time_count_nonzero(2, 100, <type 'int'>)
-        120±10μs         38.4±2μs     0.32  bench_shape_base.Block.time_3d(1, 'block')
-         156±5μs         49.3±1μs     0.32  bench_shape_base.Block2D.time_block2d((16, 16), 'uint64', (4, 4))
-        164±10μs         49.3±2μs     0.30  bench_shape_base.Block2D.time_block2d((32, 32), 'uint16', (4, 4))
-       65.7±10μs       19.6±0.7μs     0.30  bench_shape_base.Block2D.time_block2d((64, 64), 'uint16', (2, 2))
-     2.82±0.08μs         732±30ns     0.26  bench_core.CountNonzero.time_count_nonzero(1, 100, <type 'int'>)
-     2.77±0.07μs         664±30ns     0.24  bench_core.CountNonzero.time_count_nonzero(2, 100, <type 'bool'>)
-      2.61±0.1μs         624±20ns     0.24  bench_core.CountNonzero.time_count_nonzero(1, 100, <type 'bool'>)
-        16.8±3μs       3.97±0.2μs     0.24  bench_core.Core.time_hstack_l
-      2.78±0.1μs         637±20ns     0.23  bench_core.CountNonzero.time_count_nonzero(3, 100, <type 'bool'>)
-      2.36±0.2μs          207±5ns     0.09  bench_overrides.ArrayFunction.time_mock_broadcast_to_numpy
-      2.68±0.1μs          221±7ns     0.08  bench_overrides.ArrayFunction.time_mock_concatenate_numpy
-      2.58±0.1μs         201±10ns     0.08  bench_overrides.ArrayFunction.time_mock_broadcast_to_duck
-      3.02±0.2μs          222±6ns     0.07  bench_overrides.ArrayFunction.time_mock_concatenate_duck
-      4.29±0.3μs          216±6ns     0.05  bench_overrides.ArrayFunction.time_mock_concatenate_mixed
-        142±20μs          213±8ns     0.00  bench_overrides.ArrayFunction.time_mock_concatenate_many

SOME BENCHMARKS HAVE CHANGED SIGNIFICANTLY.

voir également https://docs.google.com/spreadsheets/d/15-AFI_cmZqfkU6mo2p1znsQF2E52PEXpF68QqYqEar4/edit#gid = 0 pour une feuille de calcul.

Sans surprise, la plus grande différence de performances concerne les fonctions qui appellent plusieurs fois en interne d'autres fonctions numpy, par exemple pour np.block() .

@shoyer - J'ai été un peu déconcerté par le temps supplémentaire pris ... Probablement, nous devrions vraiment avoir une implémentation C, mais en attendant j'ai fait un PR avec quelques petits changements qui font gagner du temps pour le cas courant d'un seul type, et pour le cas où le seul type est ndarray . Voir # 12321.

@shoyer - J'ai soulevé deux problèmes sur la liste de diffusion qu'il est probablement bon de

  1. Tous les types uniques d'arguments de type tableau doivent-ils être inclus dans types ? (plutôt que simplement ceux des arguments qui fournissent un remplacement.) Il semblerait utile de connaître les implémentations. (voir # 12327).
  2. L'implémentation ndarray.__array_function__ devrait-elle accepter des sous-classes même si elles remplacent __array_function__ ? Cela serait raisonnable étant donné le principe de substitution de Liskov et étant donné que la sous-classe avait déjà une chance de se libérer. Cela impliquerait d'appeler l'implémentation plutôt que la fonction publique dans ndarray.__array_function__ . (Et quelque chose de similaire dans __array_ufunc__ ...) Voir # 12328 pour un essai pour __array_function__ seulement.

@shoyer - voir # 12327 pour une implémentation rapide de (1) - si nous

Et # 12328 pour un essai de (2), principalement pour voir à quoi il ressemble.

Je suis +1 sur les deux modifications ici.

Le nom des fonctions de répartiteur dans les messages d'erreur est revenu sur https://github.com/numpy/numpy/pull/12789 , où quelqu'un a été surpris de voir TypeError: _pad_dispatcher missing 1 required positional argument

En plus des alternatives décrites ci-dessus https://github.com/numpy/numpy/issues/12028#issuecomment -429377396 (nous utilisons actuellement 2), je vais ajouter une quatrième option:

Option 4 : Écrivez un répartiteur distinct pour chaque fonction, avec le même nom que la fonction:

def sin(a):
    return (a,)


@array_function_dispatch(sin)
def sin(a):
     ...


def cos(a):
    return (a,)


@array_function_dispatch(cos)
def cos(a):
    ...

Avantages:

  • Python fournit désormais toujours les bons noms de fonction dans le message d'erreur.

Désavantages:

  • Plus de répétition de code
  • Plus d'indirection - on ne sait plus quel nom de fonction pad reçu les mauvais arguments (mais nous avons des tests pour vérifier qu'ils sont synchronisés).

Je pense que pour que le code actuel fonctionne, la fonction réelle doit venir _after_ le répartiteur.

D'accord, mais on peut lui donner le même nom que le répartiteur. Le nom du répartiteur sera écrasé.

Ce serait formidable de pouvoir définir une répartition personnalisée pour des fonctions telles que np.arange ou np.empty.

Je suppose qu'une option serait pour NumPy d'envoyer sur des scalaires ainsi que des tableaux. Est-ce incompatible avec le NEP? Quelque chose romprait-il avec ce changement?

Pour une discussion sur np.arange , voir https://github.com/numpy/numpy/issues/12379.

Je ne vois pas comment np.empty() pourrait faire le dispatching - il n'y a rien à envoyer, juste une forme et un type. Mais certainement np.empty_like() pourrait faire la distribution avec une forme écrasée - c'est exactement ce que https://github.com/numpy/numpy/pull/13046 est sur le support.

Option 4 : Écrivez un répartiteur distinct pour chaque fonction, avec le même nom que la fonction:

Y a-t-il des objections à l'adoption de cette option? Je pense que c'est probablement le choix le plus convivial du point de vue de l'utilisateur.

Je ne vois pas comment np.empty () pourrait faire du dispatching - il n'y a rien à envoyer, juste une forme et un type

Vous voudrez peut-être envoyer sur l'un ou l'autre de ceux-ci. Par exemple, voici un objet de forme personnalisé sur lequel nous pourrions vouloir distribuer différemment.

Screen Shot 2019-04-03 at 1 06 46 PM

Cet exemple n'est pas très utile, mais l'idée est que j'ai un objet paresseux qui se comporte comme une forme, mais ne renvoie pas d'entiers, il renvoie des expressions. Par exemple, ce serait bien de pouvoir faire quelque chose comme ceci:

class ExprShape:
    def __getitem__(self, i):
        return ('getitem', self, i)
    def __len__(self):
        return ('len', self)

numpy.empty(ExprShape())

Ce que je voudrais remplacer pour renvoyer quelque chose comme ExprArray('empty', ExprShape()) .

Oui, en principe, nous pourrions également expédier sur forme. Cela ajouterait une complexité / surcharge supplémentaire au protocole. Avez-vous des cas d'utilisation où l'utilisation d'un tableau comme modèle (comme empty_like avec shape ) ne suffirait pas?

Les autres cas , je peux penser est le size argument np.random.RandomState méthodes, mais notez que nous ne soutenons pas actuellement ceux du tout - voir http://www.numpy.org/ neps / nep-0018-array-function-protocol.html # callable -objects-generated-at-runtime

Avez-vous des cas d'utilisation où l'utilisation d'un tableau comme modèle (comme empty_like avec forme) ne suffirait pas?

Si nous prenons une API existante qui dépend de NumPy et que nous souhaitons la faire fonctionner de manière transparente sur un backend différent, sans changer le code source existant.

Par exemple, disons que nous essayions d'appeler scipy.optimize.differential_evolution avec des NP comme des tableaux, qui construisent un graphe d'appel au lieu de s'exécuter immédiatement.

Vous pouvez voir ici qu'il serait utile de changer np.full pour créer un tableau symbolique au lieu d'un tableau numpy par défaut, si l'entrée qui y est passée était également symbolique.

Si nous prenons une API existante qui dépend de NumPy et que nous souhaitons la faire fonctionner de manière transparente sur un backend différent, sans changer le code source existant.

Ce n'est pas possible en général. La construction de tableaux explicites comme np.array() va certainement devoir être réécrite pour être compatible avec le typage canard.

Dans ce cas, changer energies = np.full(num_members, np.inf) en energies = np.full_like(population, np.inf, shape=num_members) semble être un changement facile et lisible.

Ce n'est pas possible en général. La construction de tableaux explicites comme np.array () va certainement devoir être réécrite pour être compatible avec le typage canard.

Y a-t-il une proposition pour apporter ce type de changement ou est-ce que vous dites que soutenir la répartition de np.array serait vraiment difficile et que nous ne pourrons donc jamais obtenir un soutien à 100%?

Dans ce cas, changer energies = np.full (num_members, np.inf) en energies = np.full_like (population, np.inf, shape = num_members) semble être un changement facile et lisible.

Absolument. Mais il existe de nombreux cas où vous ne contrôlez pas le code source ou vous souhaitez aider les utilisateurs à utiliser les fonctions qu'ils connaissent et aiment autant que possible.

Il existe d'autres moyens de fournir aux utilisateurs cette expérience, comme:

  • Fournir un nouveau module qui agit comme numpy mais se comporte comme vous le souhaitez. Oblige les utilisateurs à modifier leurs importations
  • Inspectez la source pour comprendre le comportement. ala numba ou tangente.

Ces deux options peuvent être nécessaires dans certains cas (comme permettre aux utilisateurs d'appeler np.full et de renvoyer un résultat symbolique actuellement), mais si je comprends bien, le but de NEP-18 est d'essayer de limiter le moment où ceux-ci sont nécessaires et laissez les gens utiliser le NumPy original dans plus de cas.

Je comprends qu'il y a ici un compromis performance / complexité et cela pourrait être une bonne raison de ne pas les implémenter. Mais cela pourrait forcer les utilisateurs à explorer d'autres moyens pour obtenir la flexibilité qu'ils souhaitent.

Y a-t-il une proposition pour apporter ce type de changement ou est-ce que vous dites que soutenir la répartition de np.array serait vraiment difficile et que nous ne pourrons donc jamais obtenir un soutien à 100%?

NEP 22 a quelques discussions sur les options ici. Je ne pense pas que nous puissions changer en toute sécurité la sémantique de np.asarray() pour renvoyer autre chose qu'un objet numpy.ndarray - nous aurons besoin d'un nouveau protocole pour cela.

Le problème est que np.asarray() est actuellement la manière idiomatique de transtyper en un objet tableau numpy, qui utilise peut et s'attend à correspondre exactement à numpy.ndarray , par exemple jusqu'à la disposition de la mémoire.

Il y a certainement beaucoup de cas d'utilisation où ce n'est pas le cas, mais changer ce comportement briserait beaucoup de code en aval, donc ce n'est pas un démarreur. Les projets en aval devront accepter au moins cet aspect du typage de canard en réseau.

Je comprends qu'il y a ici un compromis performance / complexité et cela pourrait être une bonne raison de ne pas les implémenter. Mais cela pourrait forcer les utilisateurs à explorer d'autres moyens pour obtenir la flexibilité qu'ils souhaitent.

Oui. NEP 18 n'est pas destiné à être une solution complète pour les alternatives NumPy, mais c'est un pas dans cette direction.

J'ai rédigé une révision de NEP-18 pour ajouter un attribut __numpy_implementation__ :
https://github.com/numpy/numpy/pull/13305

Il me vient à l'esprit que l'on oublie de déformer les fonctions dans numpy.testing : https://github.com/numpy/numpy/issues/13588

Je vais faire ça sous peu ...

Il y a une révision que j'aimerais voir dans le NEP, spécifiquement pour clarifier quelles garanties NEP-18 offre aux auteurs de sous-classes: https://github.com/numpy/numpy/pull/13633

J'ai marqué les tâches d'utilisabilité comme terminées depuis que gh-13329 a été corrigé. Nous avons décidé que # 13588 peut attendre la sortie de la 1.17. Cela laisse des améliorations de la documentation et arange gh-12379 toujours ouverts pour inclusion dans 1.17.

Il y a aussi # 13728 - un bug dans le répartiteur pour histogram[2d]d

Cela laisse les améliorations de la documentation et la gamme gh-12379 toujours ouverte à l'inclusion dans la version 1.17.

Un problème de documentation manquait, j'ai donc ouvert gh-13844. Je pense que les documents sont beaucoup plus importants que le problème ouvert arange .

@shoyer pouvons-nous fermer ça?

Cette page vous a été utile?
0 / 5 - 0 notes