.__skip_array_function__
para permitir omitir el envío de __array_function__
. (https://github.com/numpy/numpy/pull/13389)numpy/core/overrides.py
en C para la velocidad (https://github.com/numpy/numpy/issues/12028):get_overloaded_types_and_args
array_function_implementation_or_override
ndarray.__array_function__
?array_function_dispatch
?numpy.core
np.core.defchararray
(# 12154)np.einsum
y np.block
(https://github.com/numpy/numpy/pull/12163)numpy.lib
numpy.fft
/ numpy.linalg
(https://github.com/numpy/numpy/pull/12117)arange?
] (https://github.com/numpy/numpy/issues/12379)ndarray.__repr__
no debe depender de __array_function__
(https://github.com/numpy/numpy/pull/12212)stacklevel
debe aumentarse en 1 para las funciones envueltas, por lo que los rastreos apuntan al lugar correcto (gh-13329)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:
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__
.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:
sin(x=1)
-> TypeError: _sin_dispatcher() got an unexpected keyword argument 'x'
.Desventajas:
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:
Desventajas:
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:
Desventajas:
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):
decorator.decorate
(# 12226): 183.694 msMi 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í:
types
? (en lugar de solo los de los argumentos que proporcionan una invalidación). Parecería útil conocer las implementaciones. (ver # 12327).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:
Desventajas:
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.
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:
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?
Comentario más útil
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 objetonumpy.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 connumpy.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.
Si. NEP 18 no pretende ser una solución completa para las alternativas de NumPy, pero es un paso en esa dirección.