Numpy: Tracking-Problem für die Implementierung von NEP-18 (__array_function__)

Erstellt am 25. Sept. 2018  ·  54Kommentare  ·  Quelle: numpy/numpy

  • [x] Kernfunktionalität zur Unterstützung von Überschreibungen:

    • [x] Erste Implementierung in reinem Python (# 12005)

    • [x] Überprüfen Sie die Dispatcher-Funktionen in array_function_dispatch (https://github.com/numpy/numpy/pull/12099).



      • Deaktivieren Sie die Validierung, wenn Sie NumPy nicht testen (wenn sich messbare Auswirkungen auf die Importzeiten ergeben) (nicht erforderlich).



    • [x] Fügen Sie ein .__skip_array_function__ -Funktionsattribut hinzu, um das Versenden von __array_function__ -Versand zu ermöglichen. (https://github.com/numpy/numpy/pull/13389)

  • [x] Teile von numpy/core/overrides.py in C für Geschwindigkeit neu implementieren (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] Unterstützungsüberschreibungen für alle öffentlichen NumPy-Funktionen

    • [x] numpy.core



      • [x] der einfache Teil (https://github.com/numpy/numpy/pull/12115)


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


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



    • [x] numpy.lib



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


      • [x] Teil 2 (# 12119)



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

    • [x] Funktionen, die derzeit vollständig in C geschrieben sind : leer_ähnlich, verkettet, inner, wobei lexsort, can_cast, min_scalar_type, result_type, Punkt, vdot, is_busday, busday_offset, busday_count, datetime_as_string (https://github.com/numpy/numpy/ ziehen / 12175)

    • [x] Linspace

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

  • [x] Verbesserungen der Benutzerfreundlichkeit

    • [x] [bessere Fehlermeldung] (https://github.com/numpy/numpy/issues/12213) für nicht implementierte Funktionen (https://github.com/numpy/numpy/pull/12251)

    • [x] ndarray.__repr__ sollte sich nicht auf __array_function__ (https://github.com/numpy/numpy/pull/12212)

    • [x] stacklevel sollte für umschlossene Funktionen um 1 erhöht werden, damit Tracebacks auf die richtige Stelle zeigen (gh-13329).

  • [x] Beheben Sie alle bekannten Fehler / nachgeschalteten Testfehler
  • [] Dokumentation

    • [x] Versionshinweise (# 12028)

    • [x] Narrative docs

    • [] Überarbeitete Dokumentzeichenfolgen zur Klärung überladener Argumente?

__array_function__

Hilfreichster Kommentar

Gibt es einen Vorschlag für diese Art von Änderung oder sagen Sie, dass die Unterstützung des Versands von np.array wirklich schwierig wäre und wir daher niemals zu 100% Unterstützung kommen können?

In NEP 22 werden die Optionen hier erörtert. Ich glaube nicht, dass wir die Semantik von np.asarray() sicher ändern können, um etwas anderes als ein numpy.ndarray Objekt zurückzugeben - wir werden dafür ein neues Protokoll benötigen.

Das Problem ist, dass np.asarray() derzeit die idiomatische Art des Castings in ein numpy-Array-Objekt ist, das can verwendet und erwartet, dass es genau mit numpy.ndarray übereinstimmt, z. B. bis zum Speicherlayout.

Es gibt sicherlich viele Anwendungsfälle, in denen dies nicht der Fall ist, aber das Umschalten dieses Verhaltens würde viele nachgelagerte Codes beschädigen, sodass es sich nicht um einen Starter handelt. Nachgelagerte Projekte müssen sich zumindest für diesen Aspekt der Array-Ententypisierung entscheiden.

Ich verstehe, dass es hier einen Kompromiss zwischen Leistung und Komplexität gibt, und das könnte ein guter Grund sein, diese nicht zu implementieren. Es könnte Benutzer jedoch dazu zwingen, andere Mittel zu erkunden, um die gewünschte Flexibilität zu erhalten.

Ja. NEP 18 soll keine vollständige Lösung für Drop-In-NumPy-Alternativen sein, ist jedoch ein Schritt in diese Richtung.

Alle 54 Kommentare

Es kann sinnvoll sein, ein vorläufiges "Dekorieren Sie alle öffentlichen NumPy-Funktionen mit @array_function_dispatch" für einige hochkarätige Funktionen zusammenzuführen und nachgeschaltete Benutzer des Protokolls aufzufordern, es auszuprobieren

Sobald wir https://github.com/numpy/numpy/pull/12099 zusammenführen, habe ich eine weitere PR bereit, die Versanddekorateure für die meisten von numpy.core hinzufügt. Es wird ziemlich einfach sein, die Dinge zu erledigen - die Zusammenstellung dauerte weniger als eine Stunde.

cc @eric-wieser @mrocklin @mhvk @hameerabbasi

Unter https://github.com/shoyer/numpy/tree/array-function-easy-impl finden Sie np.block , np.einsum und eine Handvoll Multiarray-Funktionen, die vollständig in C geschrieben sind (z. B. np.concatenate ). Ich werde dies in eine Reihe von PRs aufteilen, sobald wir mit # 12099 fertig sind.

Beachten Sie, dass ich keine Tests für Überschreibungen für jede einzelne Funktion geschrieben habe. Ich möchte ein paar Integrationstests hinzufügen, wenn wir fertig sind (z. B. ein Entenarray, das alle angewendeten Vorgänge protokolliert), aber ich denke nicht, dass es produktiv wäre, Versandtests für jede einzelne Funktion zu schreiben. Die Überprüfungen in # 12099 sollten die häufigsten Fehler bei Dispatchern auffangen, und jede Codezeile in Dispatcher-Funktionen sollte von vorhandenen Tests ausgeführt werden.

@shoyer - bei den Tests stimme ich zu, dass es nicht besonders nützlich ist, Tests für jeden einzelnen zu schreiben; Stattdessen kann es innerhalb von numpy am sinnvollsten sein, die Überschreibungen in MaskedArray relativ schnell zu verwenden.

@mhvk klingt gut für mich, obwohl ich jemanden, der MaskedArray verwendet / kennt, die Führung übernehmen lassen werde.

Siehe https://github.com/numpy/numpy/pull/12115 , https://github.com/numpy/numpy/pull/12116 , # 12119 und https://github.com/numpy/numpy/pull/ 12117 für PRs, die die Unterstützung von __array_function__ für in Python definierte Funktionen implementieren.

@shoyer -

  • Für einige Funktionen, wie reshape , bot die ursprüngliche Funktionalität bereits eine Möglichkeit, sie zu überschreiben, indem eine reshape -Methode definiert wurde. Wir lehnen dies für jede Klasse ab, die __array_function__ .
  • Bei anderen Funktionen wie np.median wurde durch die sorgfältige Verwendung von np.asanyarray und ufuncs sichergestellt, dass Unterklassen diese bereits verwenden können. Auf diese Funktionalität kann jedoch nicht mehr direkt zugegriffen werden.

Ich denke, insgesamt sind diese beiden Dinge wahrscheinlich Vorteile, da wir die Benutzeroberfläche vereinfachen und die Implementierungen für reine ndarray optimieren können - obwohl letztere vorschlägt, dass ndarray.__array_function__ die Konvertierungslisten usw. übernehmen sollte. auf ndarray , damit die Implementierungen diesen Teil überspringen können). Trotzdem dachte ich, ich würde es bemerken, da ich Angst habe, dies für Quantity ein bisschen mehr zu implementieren, als ich dachte - sowohl in Bezug auf den Arbeitsaufwand als auch auf den möglichen Leistungseinbruch.

Letzteres schlägt jedoch vor, dass ndarray .__ array_function__ die Konvertierung von Listen usw. in ndarray übernehmen sollte, damit die Implementierungen diesen Teil überspringen können.

Ich bin mir nicht sicher, ob ich hier folge.

Wir lehnen in der Tat die alte Methode zum Überschreiben von Funktionen wie reshape und mean , obwohl die alte Methode immer noch unvollständige Implementierungen der NumPy-API unterstützt.

Ich bin mir nicht sicher, ob ich hier folge.

Ich denke, das Problem ist, dass, wenn wir __array_function__ für eine einzelne Funktion implementieren, die vorherigen Mechanismen vollständig kaputt gehen und es keine Möglichkeit zum Failover gibt. Aus diesem Grund schlage ich vor, dass wir meinen NotImplementedButCoercible -Vorschlag erneut prüfen.

@hameerabbasi - ja, das ist das Problem. Obwohl wir hier vorsichtig sein müssen, wie einfach wir es machen, uns auf Klebebandlösungen zu verlassen, die wir wirklich lieber loswerden würden ... (weshalb ich oben geschrieben habe, dass meine "Probleme" tatsächlich Vorteile sein können ...) . Vielleicht gibt es einen Grund, es wie in 1.16 zu versuchen und dann über die tatsächliche Erfahrung zu entscheiden, ob wir einen Rückfall von "Ignoriere meine __array_function__ für diesen Fall" bereitstellen möchten.

Betreff: Dispatcher-Stil: Meine Einstellungen zum Stil basieren auf Überlegungen zur Speicher- / Importzeit und zur Ausführlichkeit. Führen Sie ganz einfach die Disponenten zusammen, bei denen die Signatur wahrscheinlich gleich bleibt. Auf diese Weise erstellen wir die geringste Anzahl von Objekten und die Cache-Treffer sind ebenfalls höher.

Trotzdem bin ich nicht zu sehr gegen den Lambda-Stil.

Der Stil zum Schreiben von Dispatcher-Funktionen ist jetzt in einigen PRs aufgetaucht. Es wäre gut, eine konsistente Auswahl für NumPy zu treffen.

Wir haben einige Möglichkeiten:


Option 1 : Schreiben Sie für jede Funktion einen eigenen Dispatcher, z.

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

Vorteile:

  • Sehr gut lesbar
  • Leicht zu findende Definitionen von Dispatcher-Funktionen
  • Löschen Sie die Fehlermeldung, wenn Sie die falschen Argumente angeben, z. B. sin(x=1) -> TypeError: _sin_dispatcher() got an unexpected keyword argument 'x' .

Nachteile:

  • Viele Wiederholungen, auch wenn viele Funktionen in einem Modul genau dieselbe Signatur haben.

Option 2 : Dispatcher-Funktionen innerhalb eines Moduls wiederverwenden, z.

def _unary_dispatcher(a):
    return (a,)


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


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

Vorteile:

  • Weniger Wiederholung
  • Lesbar

Nachteile:

  • Es kann etwas schwieriger sein, Definitionen von Dispatcher-Funktionen zu finden
  • Etwas weniger eindeutige Fehlermeldungen für schlechte Argumente, z. B. sin(x=1) -> TypeError: _unary_dispatcher() got an unexpected keyword argument 'x'

Option 3 : Verwenden Sie lambda -Funktionen, wenn die Dispatcher-Definition in eine Zeile passt, z.

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

Vorteile:

  • Sie müssen nicht nach Dispatcher-Definitionen suchen, sie sind genau dort.
  • Weniger Zeichen und Codezeilen.
  • Sieht für kurze Fälle (z. B. ein Argument) sehr gut aus, insbesondere wenn das Lambda kürzer als der Funktionsname ist.

Nachteile:

  • Mehr wiederholter Code als Option 2.
  • Sieht ziemlich überladen aus, wenn es mehr als ein paar Argumente gibt
  • Hat auch weniger klare Fehlermeldungen ( TypeError: <lambda>() got an unexpected keyword argument 'x' )

@shoyer : Bearbeitet, um den zweizeiligen PEP8-Abstand hinzuzufügen, um den Aspekt "Codezeilen" realistischer zu gestalten

Beachten Sie, dass die Fehlermeldungen durch Rekonstruktion des Codeobjekts behoben werden können, obwohl dies mit Kosten für die Importzeit verbunden ist. Vielleicht lohnt es sich, nachzuforschen und @nschloes Thunfisch

Ja, das Decorator-Modul könnte auch zum Generieren der Funktionsdefinition verwendet werden (es verwendet einen etwas anderen Ansatz für die Codegenerierung, ein bisschen mehr wie namedtuple, da es exec() ).

Solange der Fehler nicht behoben ist, müssen wir uns meiner Meinung nach an die Optionen eines Dispatchers halten, der einen eindeutigen Namen hat. Ich würde die Dispatcher aus Speichergründen leicht bündeln (2), würde dann aber die Fehlermeldung sehr im Auge behalten und daher vorschlagen, den Dispatcher so etwas wie _dispatch_on_x .

Wenn wir den Fehler ändern können, ändern sich die Dinge. Zum Beispiel kann es so einfach sein, Ausnahmen abzufangen, <lambda> durch den Funktionsnamen im Ausnahmetext zu ersetzen und dann erneut auszulösen. (Oder macht diese Kette heutzutage etwas?)

Ich bin damit einverstanden, dass die Fehlermeldung klar sein muss, sich im Idealfall überhaupt nicht ändern sollte.

OK, im Moment denke ich, dass es am besten ist, die Verwendung von lambda unterbrechen, es sei denn, wir haben eine Art Codegenerierung zum Laufen gebracht.

https://github.com/numpy/numpy/pull/12175 fügt einen Entwurf hinzu, wie Überschreibungen für Multiarray-Funktionen (in C geschrieben) aussehen könnten, wenn wir den Python-Wrapper-Ansatz verwenden.

@mattip wo sind wir bei der Implementierung von matmul als Ufunc? Sobald wir alle diese __array_function__ Overrides abgeschlossen haben, ist dies meiner Meinung nach das Letzte, was wir brauchen, um die öffentliche API von NumPy vollständig überladbar zu machen. Es wäre schön, alles für NumPy 1.16 bereit zu haben!

PR # 11175, das NEP 20 implementiert, hat langsame Fortschritte gemacht. Es ist ein Blocker für PR # 11133, der den Matmul-Loop-Code enthält. Dieser muss noch aktualisiert und dann über Benchmarks überprüft werden, dass der neue Code nicht langsamer als der alte ist.

Ich habe vier PRs zur Überprüfung, die den vollständigen Satz von Überschreibungen vervollständigen sollten. Abschließende Überprüfungen / Abmeldungen / Zusammenführungen sind willkommen, damit wir ernsthaft mit dem Testen von __array_function__ ! 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

Das Hinzufügen von Überschreibungen zu np.core dass einige Pandas-Tests fehlschlugen (https://github.com/pandas-dev/pandas/issues/23172). Wir sind uns noch nicht ganz sicher, was los ist, aber wir sollten es definitiv herausfinden, bevor wir es veröffentlichen.

Unter https://github.com/numpy/numpy/issues/12225 kann ich am besten erraten, warum dies zu Testfehlern bei Dask / Pandas führt.

Einige Benchmarks für Importzeiten (auf meinem MacBook Pro mit Solid-State-Laufwerk):

  • NumPy 1.15.2: 152.451 ms
  • NumPy-Master: 156,5745 ms
  • Verwenden von decorator.decorate (# 12226): 183.694 ms

Mein Benchmark-Skript

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)

Irgendeine Vorstellung von der Speichernutzung (vorher / nachher)? Das ist auch nützlich, insbesondere für IoT-Anwendungen.

Wissen Sie, wie Sie die Speichernutzung für ein Modul zuverlässig messen können?
Am Samstag, den 20. Oktober 2018 um 06:56 Uhr Hameer Abbasi [email protected]
schrieb:

Irgendeine Vorstellung von der Speichernutzung (vorher / nachher)? Das ist irgendwie nützlich als
gut, besonders für IoT-Anwendungen.

- -
Sie erhalten dies, weil Sie erwähnt wurden.
Antworte direkt auf diese E-Mail und sieh sie dir auf GitHub an
https://github.com/numpy/numpy/issues/12028#issuecomment-431584123 oder stumm schalten
der Faden
https://github.com/notifications/unsubscribe-auth/ABKS1k_IkrJ2YmYReaDrnkNvcH2X0-ZCks5umyuogaJpZM4W3kSC
.

Ich denke, ein Skript mit import numpy as np schreiben, eine Sleep-Anweisung hinzuzufügen und den Prozessspeicher zu verfolgen, sollte gut genug sein. https://superuser.com/questions/581108/how-can-i-track-and-log-cpu-and-memory-usage-on-a-mac

Möchten andere Core-Entwickler unter https://github.com/numpy/numpy/pull/12163 einen kurzen Blick darauf werfen (wirklich, es enthält nur zwei Funktionen!) array_function_dispatch zu internen Numpy-Funktionen hinzufügt.

Als Referenz ist hier der Leistungsunterschied, den ich beim Deaktivieren von __array_function__ sehe:

       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.

Eine Tabelle finden Sie auch unter https://docs.google.com/spreadsheets/d/15-AFI_cmZqfkU6mo2p1znsQF2E52PEXpF68QqYqEar4/edit#gid = 0.

Es überrascht nicht, dass der größte Leistungsunterschied bei Funktionen besteht, die andere numpy-Funktionen intern viele Male aufrufen, z. B. für np.block() .

@shoyer - Ich war etwas beunruhigt über die zusätzliche Zeit ... Wahrscheinlich sollten wir wirklich eine C-Implementierung haben, aber in der Zwischenzeit habe ich eine PR mit einigen kleinen Änderungen gemacht, die einige Zeit für den allgemeinen Fall von nur einer einzigen Zeit ndarray . Siehe # 12321.

@shoyer - Ich habe zwei Probleme auf der Mailingliste erwähnen sind:

  1. Sollten alle eindeutigen Arten von Array-ähnlichen Argumenten in types ? (und nicht nur die von Argumenten, die eine Überschreibung liefern.) Es scheint hilfreich zu sein, dies für Implementierungen zu wissen. (siehe # 12327).
  2. Sollte die Implementierung von ndarray.__array_function__ Unterklassen akzeptieren, auch wenn sie __array_function__ überschreiben? Dies wäre angesichts des Liskov-Substitutionsprinzips und angesichts der Tatsache, dass die Unterklasse bereits eine Chance auf Kaution hatte, angemessen. Dies würde bedeuten, dass die Implementierung und nicht die öffentliche Funktion in ndarray.__array_function__ . (Und etwas Ähnliches in __array_ufunc__ ...) Siehe # 12328 für eine Testversion nur für __array_function__ .

@shoyer - siehe # 12327 für eine schnelle Implementierung von (1) - wenn wir diesen Weg gehen, sollten wir auch den NEP anpassen.

Und # 12328 für einen Test von (2), hauptsächlich um zu sehen, wie es aussieht.

Ich bin +1 bei beiden Modifikationen hier.

Der Name der Dispatcher-Funktionen in Fehlermeldungen wurde erneut unter https://github.com/numpy/numpy/pull/12789 angezeigt , wo jemand überrascht war, TypeError: _pad_dispatcher missing 1 required positional argument

Zusätzlich zu den oben beschriebenen Alternativen https://github.com/numpy/numpy/issues/12028#issuecomment -429377396 (wir verwenden derzeit 2) werde ich eine vierte Option hinzufügen:

Option 4 : Schreiben Sie für jede Funktion einen separaten Dispatcher mit demselben Namen wie die Funktion:

def sin(a):
    return (a,)


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


def cos(a):
    return (a,)


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

Vorteile:

  • Python liefert jetzt immer die richtigen Funktionsnamen in der Fehlermeldung.

Nachteile:

  • Mehr Code-Wiederholung
  • Mehr Indirektion - es ist nicht mehr klar, welcher Funktionsname pad die falschen Argumente erhalten hat (aber wir haben Tests, um zu überprüfen, ob sie synchron gehalten werden).

Ich denke, um den aktuellen Code am Laufen zu halten, muss die eigentliche Funktion nach dem Dispatcher kommen.

Richtig, aber wir können ihm den gleichen Namen geben wie dem Dispatcher. Der Name des Dispatchers wird überschrieben.

Es wäre großartig, benutzerdefiniertes Dispatching für Funktionen wie np.arange oder np.empty definieren zu können.

Ich denke, eine Option wäre, dass NumPy sowohl auf Skalaren als auch auf Arrays versendet. Ist das nicht mit der NEP vereinbar? Würde irgendetwas mit dieser Änderung brechen?

Weitere Informationen zu np.arange finden Sie unter https://github.com/numpy/numpy/issues/12379.

Ich sehe nicht ein, wie np.empty() den Versand durchführen könnte - es gibt nichts zu versenden, nur eine Form und einen D-Typ. Aber sicherlich könnte np.empty_like() mit einer überschriebenen Form versenden - genau darum geht es bei https://github.com/numpy/numpy/pull/13046 .

Option 4 : Schreiben Sie für jede Funktion einen separaten Dispatcher mit demselben Namen wie die Funktion:

Haben Sie Einwände gegen die Einführung dieser Option? Ich denke, es ist aus Anwendersicht wahrscheinlich die freundlichste Wahl.

Ich sehe nicht ein, wie np.empty () den Versand durchführen könnte - es gibt nichts zu versenden, nur eine Form und einen D-Typ

Möglicherweise möchten Sie eine der beiden versenden. Hier ist beispielsweise ein benutzerdefiniertes Formobjekt, auf das wir möglicherweise anders versenden möchten.

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

Dieses Beispiel ist nicht sehr nützlich, aber die Idee ist, dass ich ein faules Objekt habe, das sich wie eine Form verhält, aber keine ganzen Zahlen zurückgibt, sondern Ausdrücke zurückgibt. Zum Beispiel wäre es schön, so etwas tun zu können:

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

numpy.empty(ExprShape())

Was ich überschreiben möchte, um so etwas wie ExprArray('empty', ExprShape()) .

Ja, im Prinzip könnten wir auch in Form versenden. Dies würde dem Protokoll zusätzliche Komplexität / zusätzlichen Aufwand hinzufügen. Haben Sie Anwendungsfälle, in denen die Verwendung eines Arrays als Vorlage (wie empty_like mit shape ) nicht ausreicht?

Die anderen Fälle, an die ich denken kann, sind das Argument size für np.random.RandomState Methoden, aber beachten Sie, dass wir diese derzeit überhaupt nicht unterstützen - siehe http://www.numpy.org/ neps / nep-0018-array-function-protocol.html # aufrufbare -Objekte-generiert-zur Laufzeit

Haben Sie Anwendungsfälle, in denen die Verwendung eines Arrays als Vorlage (wie leer_ähnlich mit Form) nicht ausreicht?

Wenn wir eine vorhandene API verwenden, die von NumPy abhängt und transparent auf einem anderen Backend arbeiten soll, ohne den vorhandenen Quellcode zu ändern.

Nehmen wir zum Beispiel an, wir haben versucht, scipy.optimize.differential_evolution mit NP-ähnlichen Arrays aufzurufen, die ein Aufrufdiagramm erstellen, anstatt es sofort auszuführen.

Sie können hier sehen , dass es hilfreich wäre, wenn wir np.full ändern könnten, um ein symbolisches Array anstelle eines Standard-Numpy-Arrays zu erstellen, wenn die darin übergebene Eingabe auch symbolisch wäre.

Wenn wir eine vorhandene API verwenden, die von NumPy abhängt und transparent auf einem anderen Backend arbeiten soll, ohne den vorhandenen Quellcode zu ändern.

Dies ist im Allgemeinen nicht möglich. Explizite Array-Konstruktionen wie np.array() definitiv neu geschrieben werden, um mit der Ententypisierung kompatibel zu sein.

In diesem Fall scheint das Umschalten von energies = np.full(num_members, np.inf) auf energies = np.full_like(population, np.inf, shape=num_members) eine einfache und lesbare Änderung zu sein.

Dies ist im Allgemeinen nicht möglich. Explizite Array-Konstruktionen wie np.array () müssen definitiv neu geschrieben werden, um mit der Ententypisierung kompatibel zu sein.

Gibt es einen Vorschlag für diese Art von Änderung oder sagen Sie, dass die Unterstützung des Versands von np.array wirklich schwierig wäre und wir daher niemals zu 100% Unterstützung kommen können?

In diesem Fall scheint das Umschalten von Energien = np.full (num_members, np.inf) auf Energien = np.full_like (Bevölkerung, np.inf, Form = num_members) eine einfache und lesbare Änderung zu sein.

Bestimmt. Es gibt jedoch viele Fälle, in denen Sie entweder den Quellcode nicht kontrollieren oder Benutzer dabei unterstützen möchten, die Funktionen, die sie kennen und lieben, so gut wie möglich zu nutzen.

Es gibt andere Möglichkeiten, Benutzern diese Erfahrung zu bieten:

  • Bereitstellung eines neuen Moduls, das sich wie Numpy verhält, sich aber so verhält, wie Sie es möchten. Erfordert, dass Benutzer ihre Importe ändern
  • Untersuchen Sie die Quelle, um das Verhalten zu verstehen. ala numba oder tangential.

Diese beiden Optionen sind möglicherweise in bestimmten Fällen erforderlich (z. B. wenn Benutzer np.full aufrufen und derzeit ein symbolisches Ergebnis zurückgeben). Wenn ich das jedoch richtig verstehe, besteht das Ziel von NEP-18 darin, zu versuchen, zu begrenzen, wann diese benötigt werden und lassen Sie die Leute in mehr Fällen das Original NumPy verwenden.

Ich verstehe, dass es hier einen Kompromiss zwischen Leistung und Komplexität gibt, und das könnte ein guter Grund sein, diese nicht zu implementieren. Es könnte Benutzer jedoch dazu zwingen, andere Mittel zu erkunden, um die gewünschte Flexibilität zu erhalten.

Gibt es einen Vorschlag für diese Art von Änderung oder sagen Sie, dass die Unterstützung des Versands von np.array wirklich schwierig wäre und wir daher niemals zu 100% Unterstützung kommen können?

In NEP 22 werden die Optionen hier erörtert. Ich glaube nicht, dass wir die Semantik von np.asarray() sicher ändern können, um etwas anderes als ein numpy.ndarray Objekt zurückzugeben - wir werden dafür ein neues Protokoll benötigen.

Das Problem ist, dass np.asarray() derzeit die idiomatische Art des Castings in ein numpy-Array-Objekt ist, das can verwendet und erwartet, dass es genau mit numpy.ndarray übereinstimmt, z. B. bis zum Speicherlayout.

Es gibt sicherlich viele Anwendungsfälle, in denen dies nicht der Fall ist, aber das Umschalten dieses Verhaltens würde viele nachgelagerte Codes beschädigen, sodass es sich nicht um einen Starter handelt. Nachgelagerte Projekte müssen sich zumindest für diesen Aspekt der Array-Ententypisierung entscheiden.

Ich verstehe, dass es hier einen Kompromiss zwischen Leistung und Komplexität gibt, und das könnte ein guter Grund sein, diese nicht zu implementieren. Es könnte Benutzer jedoch dazu zwingen, andere Mittel zu erkunden, um die gewünschte Flexibilität zu erhalten.

Ja. NEP 18 soll keine vollständige Lösung für Drop-In-NumPy-Alternativen sein, ist jedoch ein Schritt in diese Richtung.

Ich habe eine Überarbeitung von NEP-18 entworfen, um ein __numpy_implementation__ -Attribut hinzuzufügen:
https://github.com/numpy/numpy/pull/13305

Mir fällt ein, dass wir vergessen haben, die Funktionen in numpy.testing zu verzerren: https://github.com/numpy/numpy/issues/13588

Ich werde das in Kürze tun ...

Es gibt eine Revision, die ich gerne bei der NEP sehen würde, insbesondere um zu klären, welche Garantien NEP-18 für Autoren von Unterklassen bietet: https://github.com/numpy/numpy/pull/13633

Ich habe die Usability-Aufgaben als abgeschlossen markiert, seit gh-13329 behoben wurde. Wir haben entschieden, dass # 13588 bis nach der Veröffentlichung von 1.17 warten kann. Damit bleiben Dokumentationsverbesserungen und arange gh-12379 für die Aufnahme in 1.17 offen.

Es gibt auch # 13728 - ein Fehler im Dispatcher für histogram[2d]d

Damit bleiben Dokumentationsverbesserungen und der Bereich gh-12379 für die Aufnahme in 1.17 offen.

Ein Dokumentationsproblem fehlte, daher öffnete ich gh-13844. Ich denke, Dokumente sind viel wichtiger als das offene Problem arange .

@shoyer können wir das schließen?

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

qualiaa picture qualiaa  ·  3Kommentare

dmvianna picture dmvianna  ·  4Kommentare

kevinzhai80 picture kevinzhai80  ·  4Kommentare

Kreol64 picture Kreol64  ·  3Kommentare

thouis picture thouis  ·  4Kommentare