Numpy: Problema de seguimiento para la implementación de NEP-18 (__array_function__)

Creado en 25 sept. 2018  ·  54Comentarios  ·  Fuente: numpy/numpy

  • [x] Funcionalidad principal para admitir anulaciones:

    • [x] Implementación inicial en Python puro (# 12005)

    • [x] Validar las funciones del despachador en array_function_dispatch (https://github.com/numpy/numpy/pull/12099)



      • Deshabilite la validación cuando no esté probando NumPy (si hay un impacto medible en los tiempos de importación) (innecesario)



    • [x] Agregue un atributo de función .__skip_array_function__ para permitir omitir el envío de __array_function__ . (https://github.com/numpy/numpy/pull/13389)

  • [x] Reimplementar partes de numpy/core/overrides.py en C para la velocidad (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] Soporta anulaciones para todas las funciones públicas de NumPy

    • [x] numpy.core



      • [x] la parte fácil (https://github.com/numpy/numpy/pull/12115)


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


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



    • [x] numpy.lib



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


      • [x] parte 2 (# 12119)



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

    • [x] funciones actualmente escritas completamente 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/ tirar / 12175)

    • [x] linspace

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

  • [x] Mejoras en la usabilidad

    • [x] [mejor mensaje de error] (https://github.com/numpy/numpy/issues/12213) para funciones no implementadas (https://github.com/numpy/numpy/pull/12251)

    • [x] ndarray.__repr__ no debe depender de __array_function__ (https://github.com/numpy/numpy/pull/12212)

    • [x] stacklevel debe aumentarse en 1 para las funciones envueltas, por lo que los rastreos apuntan al lugar correcto (gh-13329)

  • [x] Corregir todos los errores conocidos / fallas de pruebas posteriores
  • [] Documentación

    • [x] Notas de la versión (n.º 12028)

    • [x] Documentos narrativos

    • [] ¿Se han revisado las cadenas de documentos para aclarar los argumentos sobrecargados?

__array_function__

Comentario más útil

¿Existe una propuesta para hacer este tipo de cambio o está diciendo que respaldar el envío de np.array sería realmente difícil y que nunca podríamos llegar al 100% de respaldo?

NEP 22 tiene algunas discusiones sobre las opciones aquí. No creo que podamos cambiar de forma segura la semántica de np.asarray() para devolver algo que no sea un objeto numpy.ndarray ; necesitaremos un nuevo protocolo para esto.

El problema es que np.asarray() es actualmente la forma idiomática de convertir a un objeto de matriz numpy, que usa can y espera coincidir exactamente con numpy.ndarray , por ejemplo, hasta el diseño de la memoria.

Ciertamente, hay muchos casos de uso en los que este no es el caso, pero cambiar este comportamiento rompería una gran cantidad de código descendente, por lo que no es un principio. Los proyectos posteriores deberán optar por participar al menos en este aspecto de la tipificación de pato de matriz.

Entiendo que hay una compensación de rendimiento / complejidad aquí y esa podría ser una buena razón para no implementarlos. Pero podría obligar a los usuarios a explorar otros medios para obtener la flexibilidad que desean.

Si. NEP 18 no pretende ser una solución completa para las alternativas de NumPy, pero es un paso en esa dirección.

Todos 54 comentarios

Podría ser bueno fusionar un preliminar "Decorar todas las funciones públicas de NumPy con @array_function_dispatch" para algunas funciones de alto perfil y solicitar a los consumidores posteriores del protocolo que lo prueben

Una vez que fusionamos https://github.com/numpy/numpy/pull/12099 tengo otro PR listo que agregará decoradores de despacho para la mayoría de numpy.core . Será bastante fácil terminar las cosas: este tomó menos de una hora para armarlo.

cc @ eric-wieser @mrocklin @mhvk @hameerabbasi

Consulte https://github.com/shoyer/numpy/tree/array-function-easy-impl para ver mi rama que implementa todas las anulaciones "fáciles" en funciones con envoltorios de Python. Las partes sobrantes son np.block , np.einsum y un puñado de funciones multiarray escritas completamente en C (por ejemplo, np.concatenate ). Dividiré esto en un montón de relaciones públicas una vez que terminemos con # 12099.

Tenga en cuenta que no he escrito pruebas para anulaciones en cada función individual. Me gustaría agregar algunas pruebas de integración cuando hayamos terminado (por ejemplo, una matriz de pato que registra todas las operaciones aplicadas), pero no creo que sea productivo escribir pruebas de despacho para cada función individual. Las comprobaciones en # 12099 deberían detectar los errores más comunes en los despachadores, y cada línea de código en las funciones del despachador debería ser ejecutada por las pruebas existentes.

@shoyer - en las pruebas, estoy de acuerdo en que no es particularmente útil escribir pruebas para cada una; en cambio, dentro de numpy, puede tener más sentido comenzar a usar las invalidaciones relativamente rápido en MaskedArray .

@mhvk me suena bien, aunque dejaré que otra persona que use / conozca MaskedArray tome la iniciativa.

Consulte https://github.com/numpy/numpy/pull/12115 , https://github.com/numpy/numpy/pull/12116 , # 12119 y https://github.com/numpy/numpy/pull/ 12117 para RP que implementan el soporte __array_function__ en funciones definidas en Python.

@shoyer : al ver algunas de las implementaciones, tengo dos preocupaciones:

  • Para algunas funciones, como reshape , la funcionalidad original ya proporcionaba una forma de anularla, definiendo un método reshape . De hecho, estamos desaprobando eso para cualquier clase que defina __array_function__ .
  • Para otras funciones, como np.median , el uso cuidadoso de np.asanyarray y ufuncs aseguró que las subclases ya pudieran usarlas. Pero ya no se puede acceder directamente a esa funcionalidad.

Creo que, en general, estas dos cosas son probablemente beneficios, ya que simplificamos la interfaz y podemos optimizar las implementaciones para ndarray puros, aunque este último sugiere que ndarray.__array_function__ debería hacerse cargo de las listas de conversión, etc. a ndarray , para que las implementaciones puedan omitir esa parte). Aún así, pensé en notarlo ya que me da miedo implementar esto por Quantity un poco más de lo que pensaba, en términos tanto de la cantidad de trabajo como del posible impacto en el rendimiento.

aunque el último sugiere que ndarray .__ array_function__ debería hacerse cargo de la conversión de listas, etc., a ndarray, para que las implementaciones puedan omitir esa parte).

No estoy seguro de seguir aquí.

De hecho, estamos desaprobando efectivamente la forma antigua de anular funciones como reshape y mean , aunque la forma antigua todavía admite implementaciones incompletas de la API de NumPy.

No estoy seguro de seguir aquí.

Creo que el problema es que si implementamos __array_function__ incluso para una sola función, los mecanismos anteriores se rompen por completo y no hay forma de conmutar por error. Por eso propongo que revisemos mi propuesta NotImplementedButCoercible .

@hameerabbasi - sí, ese es el problema. Aunque debemos tener cuidado aquí con lo fácil que hacemos para confiar en soluciones de cinta adhesiva de las que realmente preferiríamos deshacernos ... (por eso escribí anteriormente que mis "problemas" en realidad pueden ser beneficios ...) . Tal vez haya un caso para intentarlo como está en 1.16 y luego decidir sobre la experiencia real si queremos proporcionar un respaldo de "ignorar mi __array_function__ para este caso".

Re: estilo del despachador: Mis preferencias de estilo se basan en consideraciones de tiempo de memoria / importación y verbosidad. Simplemente, combine los despachadores donde es probable que la firma siga siendo la misma. De esta manera, creamos la menor cantidad de objetos y los hits de caché también serán mayores.

Dicho esto, no me opongo demasiado al estilo lambda.

El estilo para escribir funciones de despachador ahora ha surgido en algunos PR. Sería bueno hacer una elección consistente en NumPy.

Tenemos algunas opciones:


Opción 1 : escriba un despachador separado para cada función, por ejemplo,

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):
    ...

Ventajas:

  • Muy legible
  • Definiciones fáciles de encontrar de las funciones del despachador
  • Borre el mensaje de error cuando proporcione los argumentos incorrectos, por ejemplo, sin(x=1) -> TypeError: _sin_dispatcher() got an unexpected keyword argument 'x' .

Desventajas:

  • Mucha repetición, incluso cuando muchas funciones en un módulo tienen exactamente la misma firma.

Opción 2 : reutilizar las funciones del despachador dentro de un módulo, por ejemplo,

def _unary_dispatcher(a):
    return (a,)


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


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

Ventajas:

  • Menos repetición
  • Legible

Desventajas:

  • Puede ser un poco más difícil encontrar definiciones de funciones de despachador
  • Mensajes de error un poco menos claros para los malos argumentos, por ejemplo, sin(x=1) -> TypeError: _unary_dispatcher() got an unexpected keyword argument 'x'

Opción 3 : use las funciones lambda cuando la definición del despachador quepa en una línea, por ejemplo,

# 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):
     ...

Ventajas:

  • No es necesario buscar definiciones de despachador, están ahí.
  • Menor número de caracteres y líneas de códigos.
  • Se ve muy bien para casos cortos (por ejemplo, un argumento), especialmente cuando la lambda es más corta que el nombre de la función.

Desventajas:

  • Código más repetido que la opción 2.
  • Parece bastante desordenado si hay más de unos pocos argumentos
  • También tiene mensajes de error menos claros ( TypeError: <lambda>() got an unexpected keyword argument 'x' )

@shoyer : editado para agregar el espaciado PEP8 de dos líneas para hacer que el aspecto de "líneas de código" sea más realista

Tenga en cuenta que los problemas con los mensajes de error se pueden solucionar reconstruyendo el objeto de código , aunque eso conllevará algún costo de tiempo de importación. Quizás valga la pena investigar y separar el atún de

Sí, el módulo decorador también podría usarse para generar la definición de función (usa un enfoque ligeramente diferente para la generación de código, un poco más como namedtuple en que usa exec() ).

Mientras no se resuelva el error, creo que debemos ceñirnos a las opciones con un despachador que tenga un nombre claro. Me gustaría unir un poco a los despachadores (2) por razones de memoria, aunque entonces tendría muy en cuenta el mensaje de error, por lo que sugeriría llamar al despachador algo como _dispatch_on_x .

Aunque si podemos cambiar el error, las cosas cambiarán. Por ejemplo, podría ser tan simple como detectar excepciones, reemplazar <lambda> con el nombre de la función en el texto de la excepción y luego volver a generar. (¿O esa cosa de la cadena estos días?)

Estoy de acuerdo en que el mensaje de error debe ser claro, idealmente no debería cambiar en absoluto.

De acuerdo, por ahora creo que es mejor dejar de usar lambda , a menos que tengamos algún tipo de generación de código funcionando.

https://github.com/numpy/numpy/pull/12175 agrega un borrador de cómo se verían las anulaciones para las funciones de múltiples matrices (escritas en C) si adoptamos el enfoque de envoltura de Python.

@mattip ¿ Dónde estamos al implementar matmul como ufunc? Una vez que terminemos todas estas anulaciones de __array_function__ , creo que eso es lo último que necesitamos para hacer que la API pública de NumPy sea completamente sobrecargable. ¡Sería bueno tenerlo todo listo para NumPy 1.16!

El PR # 11175, que implementa NEP 20, ha avanzado lentamente. Es un bloqueador para PR # 11133, que tiene el código de bucle matmul. Ese todavía necesita ser actualizado y luego verificado a través de puntos de referencia para que el nuevo código no sea más lento que el anterior.

Tengo cuatro RP pendientes de revisión que deberían completar el conjunto completo de anulaciones. Se agradecerían las revisiones / aprobaciones / fusiones finales para que podamos comenzar a probar __array_function__ en serio. 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

Agregar anulaciones a np.core provocó que algunas pruebas de pandas fallaran (https://github.com/pandas-dev/pandas/issues/23172). No estamos muy seguros de lo que está pasando todavía, pero definitivamente deberíamos averiguarlo antes de lanzarlo.

Consulte https://github.com/numpy/numpy/issues/12225 para mi mejor suposición de por qué esto está causando fallas en las pruebas en dask / pandas.

Algunos puntos de referencia de los tiempos de importación (en mi macbook pro con una unidad de estado sólido):

  • NumPy 1.15.2: 152.451 ms
  • Maestro NumPy: 156,5745 ms
  • Usando decorator.decorate (# 12226): 183.694 ms

Mi guión de referencia

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)

¿Alguna idea del uso de la memoria (antes / después)? Eso también es algo útil, especialmente para aplicaciones de IoT.

¿Sabe cómo medir de forma fiable el uso de memoria de un módulo?
El sábado, 20 de octubre de 2018 a las 6:56 a.m. Hameer Abbasi [email protected]
escribió:

¿Alguna idea del uso de la memoria (antes / después)? Eso es algo útil como
bueno, especialmente para aplicaciones de IoT.

-
Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/numpy/numpy/issues/12028#issuecomment-431584123 , o silenciar
la amenaza
https://github.com/notifications/unsubscribe-auth/ABKS1k_IkrJ2YmYReaDrnkNvcH2X0-ZCks5umyuogaJpZM4W3kSC
.

Creo que escribir un script que contenga import numpy as np , agregar una declaración de suspensión y rastrear la memoria del proceso debería ser lo suficientemente bueno. https://superuser.com/questions/581108/how-can-i-track-and-log-cpu-and-memory-usage-on-a-mac

¿Algún otro desarrollador central quiere echar un vistazo rápido (en realidad, solo incluye dos funciones) en https://github.com/numpy/numpy/pull/12163? Es el último RP que agrega array_function_dispatch a las funciones internas de numpy.

Como referencia, aquí está la diferencia de rendimiento que veo al deshabilitar __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.

consulte también https://docs.google.com/spreadsheets/d/15-AFI_cmZqfkU6mo2p1znsQF2E52PEXpF68QqYqEar4/edit#gid = 0 para una hoja de cálculo.

No es sorprendente que la mayor diferencia de rendimiento sea para funciones que llaman a otras funciones numerosas internamente muchas veces, por ejemplo, por np.block() .

@shoyer : estaba un poco desconcertado por el tiempo extra que tomó ... Probablemente, deberíamos tener una implementación de C, pero mientras tanto hice un PR con algunos pequeños cambios que reducen algo de tiempo para el caso común de solo una type, y para el caso donde el único tipo es ndarray . Ver # 12321.

@shoyer : planteé dos cuestiones en la lista de correo que probablemente también sea bueno mencionar aquí:

  1. ¿Deberían incluirse todos los tipos únicos de argumentos tipo matriz en types ? (en lugar de solo los de los argumentos que proporcionan una invalidación). Parecería útil conocer las implementaciones. (ver # 12327).
  2. ¿Debería la implementación ndarray.__array_function__ aceptar subclases incluso si anulan __array_function__ ? Esto sería razonable dado el principio de sustitución de Liskov y dado que la subclase ya tenía la oportunidad de abandonar. Implicaría llamar a la implementación en lugar de a la función pública dentro de ndarray.__array_function__ . (Y algo similar en __array_ufunc__ ...) Vea # 12328 para una prueba por __array_function__ solamente.

@shoyer - ver # 12327 para una implementación rápida de (1) - si tomamos esta ruta, creo que también deberíamos ajustar el NEP.

Y # 12328 para una prueba de (2), principalmente para ver cómo se ve.

Soy +1 en ambas modificaciones aquí.

El nombre de las funciones del despachador en los mensajes de error apareció nuevamente en https://github.com/numpy/numpy/pull/12789 , donde alguien se sorprendió al ver TypeError: _pad_dispatcher missing 1 required positional argument

Además de las alternativas descritas anteriormente https://github.com/numpy/numpy/issues/12028#issuecomment -429377396 (actualmente usamos 2), agregaré una cuarta opción:

Opción 4 : escriba un despachador independiente para cada función, con el mismo nombre que la función:

def sin(a):
    return (a,)


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


def cos(a):
    return (a,)


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

Ventajas:

  • Python ahora siempre proporciona los nombres de funciones correctos en el mensaje de error.

Desventajas:

  • Más repetición de código
  • Más indirecta: ya no está claro qué nombre de función pad recibió los argumentos incorrectos (pero tenemos pruebas para verificar que se mantengan sincronizados).

Creo que para mantener el código actual funcionando, la función real debe venir _después_ del despachador.

Correcto, pero podemos darle el mismo nombre que el despachador. Se sobrescribirá el nombre del despachador.

Sería genial poder definir el envío personalizado para funciones como np.arange o np.empty.

Supongo que una opción sería que NumPy distribuyera tanto en escalares como en matrices. ¿Es esto incompatible con la NEP? ¿Algo rompería con este cambio?

Para obtener información sobre np.arange , consulte https://github.com/numpy/numpy/issues/12379.

No veo cómo np.empty() podría hacer el envío; no hay nada para enviar, solo una forma y un tipo. Pero ciertamente np.empty_like() podría hacer el envío con una forma sobrescrita; eso es exactamente lo que https://github.com/numpy/numpy/pull/13046 trata de brindar soporte.

Opción 4 : escriba un despachador independiente para cada función, con el mismo nombre que la función:

¿Alguna objeción a la adopción de esta opción? Creo que es probablemente la opción más amigable desde la perspectiva del usuario.

No veo cómo np.empty () podría hacer el envío; no hay nada para enviar, solo una forma y un tipo de letra

Es posible que desee enviar en cualquiera de esos. Por ejemplo, aquí hay un objeto de forma personalizada, que podríamos querer enviar de manera diferente.

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

Este ejemplo no es muy útil, pero la idea es que tengo un objeto perezoso que se comporta como una forma, pero no devuelve enteros, devuelve expresiones. Por ejemplo, sería bueno poder hacer algo como esto:

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

numpy.empty(ExprShape())

¿Cuál me gustaría anular para devolver algo como ExprArray('empty', ExprShape()) .

Sí, en principio también podríamos despachar en forma. Eso agregaría complejidad / gastos generales adicionales al protocolo. ¿Tiene casos de uso en los que usar una matriz como plantilla (como empty_like con shape ) no sería suficiente?

Los otros casos que se me ocurre es el size argumento a np.random.RandomState métodos, pero tenga en cuenta que actualmente no son compatibles con los de todos - ver http://www.numpy.org/ neps / nep-0018-array-function-protocol.html # invocables -objects-generate-at-runtime

¿Tiene casos de uso en los que usar una matriz como plantilla (como empty_like con shape) no sería suficiente?

Si estamos tomando una API existente que depende de NumPy y nos gustaría que funcione de manera transparente en un backend diferente, sin cambiar el código fuente existente.

Por ejemplo, digamos que estamos intentando llamar a scipy.optimize.differential_evolution con arreglos similares a NP, que crean un gráfico de llamadas en lugar de ejecutarse inmediatamente.

Puede ver aquí que sería útil si pudiéramos cambiar np.full para crear una matriz simbólica en lugar de una matriz numérica predeterminada, si la entrada pasada también fuera simbólica.

Si estamos tomando una API existente que depende de NumPy y nos gustaría que funcione de manera transparente en un backend diferente, sin cambiar el código fuente existente.

En general, esto no es posible. La construcción explícita de matrices como np.array() definitivamente necesitará ser reescrita para que sea compatible con la escritura pato.

En este caso, cambiar energies = np.full(num_members, np.inf) a energies = np.full_like(population, np.inf, shape=num_members) parece un cambio fácil y legible.

En general, esto no es posible. La construcción explícita de matrices como np.array () definitivamente necesitará ser reescrita para que sea compatible con la escritura pato.

¿Existe una propuesta para hacer este tipo de cambio o está diciendo que respaldar el envío de np.array sería realmente difícil y que nunca podríamos llegar al 100% de respaldo?

En este caso, cambiar energías = np.full (num_members, np.inf) a energías = np.full_like (población, np.inf, shape = num_members) parece un cambio fácil y legible.

Seguro. Pero hay muchos casos en los que no controla el código fuente o desea ayudar a los usuarios a usar las funciones que conocen y aman tanto como sea posible.

Hay otras formas de brindarles a los usuarios esa experiencia, como:

  • Proporcionar un nuevo módulo que actúa como numpy pero se comporta como le gustaría. Requiere que los usuarios cambien sus importaciones
  • Inspeccione la fuente para comprender el comportamiento. ala numba o tangente.

Ambas opciones pueden ser necesarias en ciertos casos (como permitir que los usuarios llamen a np.full y devuelvan un resultado simbólico actualmente), pero si entiendo correctamente, el objetivo de NEP-18 es tratar de limitar cuándo se necesitan. y dejar que la gente use el NumPy original en más casos.

Entiendo que hay una compensación de rendimiento / complejidad aquí y esa podría ser una buena razón para no implementarlos. Pero podría obligar a los usuarios a explorar otros medios para obtener la flexibilidad que desean.

¿Existe una propuesta para hacer este tipo de cambio o está diciendo que respaldar el envío de np.array sería realmente difícil y que nunca podríamos llegar al 100% de respaldo?

NEP 22 tiene algunas discusiones sobre las opciones aquí. No creo que podamos cambiar de forma segura la semántica de np.asarray() para devolver algo que no sea un objeto numpy.ndarray ; necesitaremos un nuevo protocolo para esto.

El problema es que np.asarray() es actualmente la forma idiomática de convertir a un objeto de matriz numpy, que usa can y espera coincidir exactamente con numpy.ndarray , por ejemplo, hasta el diseño de la memoria.

Ciertamente, hay muchos casos de uso en los que este no es el caso, pero cambiar este comportamiento rompería una gran cantidad de código descendente, por lo que no es un principio. Los proyectos posteriores deberán optar por participar al menos en este aspecto de la tipificación de pato de matriz.

Entiendo que hay una compensación de rendimiento / complejidad aquí y esa podría ser una buena razón para no implementarlos. Pero podría obligar a los usuarios a explorar otros medios para obtener la flexibilidad que desean.

Si. NEP 18 no pretende ser una solución completa para las alternativas de NumPy, pero es un paso en esa dirección.

He redactado una revisión de NEP-18 para agregar un atributo __numpy_implementation__ :
https://github.com/numpy/numpy/pull/13305

Se me ocurre que nos olvidamos de deformar las funciones en numpy.testing : https://github.com/numpy/numpy/issues/13588

Voy a hacer eso en breve ...

Hay una revisión que me gustaría ver en la NEP, específicamente para aclarar qué garantías ofrece NEP-18 a los autores de subclase: https://github.com/numpy/numpy/pull/13633

Marqué las tareas de usabilidad completadas desde que se corrigió gh-13329. Decidimos que el # 13588 puede esperar hasta después del lanzamiento de 1.17. Eso deja las mejoras de documentación y arange gh-12379 aún abiertos para su inclusión en 1.17.

También hay # 13728 - un error en el despachador por histogram[2d]d

Eso deja las mejoras de documentación y el rango gh-12379 aún abiertos para su inclusión en 1.17.

Faltaba un problema de documentación, así que abrí gh-13844. Creo que los documentos son mucho más importantes que el problema abierto arange .

@shoyer ¿podemos cerrar esto?

¿Fue útil esta página
0 / 5 - 0 calificaciones