Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 357 – Erlauben beliebiger Objekte für Slicing

Autor:
Travis Oliphant <oliphant at ee.byu.edu>
Status:
Final
Typ:
Standards Track
Erstellt:
09-Feb-2006
Python-Version:
2.5
Post-History:


Inhaltsverzeichnis

Zusammenfassung

Dieser PEP schlägt die Hinzufügung eines nb_index Slots in PyNumberMethods und einer speziellen Methode __index__ vor, damit beliebige Objekte verwendet werden können, wo immer Ganzzahlen explizit in Python benötigt werden, wie z. B. in der Slice-Syntax (woher der Slot seinen Namen hat).

Begründung

Derzeit spielen Ganzzahlen und lange Ganzzahlen eine Sonderrolle beim Slicing, da sie die einzigen Objekte sind, die in der Slice-Syntax zulässig sind. Mit anderen Worten, wenn X ein Objekt ist, das das Sequenzprotokoll implementiert, dann ist X[obj1:obj2] nur dann gültig, wenn obj1 und obj2 beide Ganzzahlen oder lange Ganzzahlen sind. Es gibt keine Möglichkeit für obj1 und obj2, Python mitzuteilen, dass sie vernünftigerweise als Indizes in eine Sequenz verwendet werden könnten. Dies ist eine unnötige Einschränkung.

In NumPy zum Beispiel gibt es 8 verschiedene Ganzzahl-Skalare, die vorzeichenlosen und vorzeichenbehafteten Ganzzahlen mit 8, 16, 32 und 64 Bits entsprechen. Diese Typobjekte könnten vernünftigerweise als Ganzzahlen an vielen Stellen verwendet werden, an denen Python echte Ganzzahlen erwartet, aber aus Gründen der inkompatiblen Speicherlayouts nicht vom Python-Ganzzahltyp erben können. Es sollte eine Möglichkeit geben, Python mitzuteilen, dass ein Objekt sich wie eine Ganzzahl verhalten kann.

Zu diesem Zweck kann die Methode nb_int (und die spezielle Methode __int__) nicht verwendet werden, da diese Methode verwendet wird, um Objekte in Ganzzahlen zu *koorzieren*. Es wäre unangemessen, jedem Objekt, das zu einer Ganzzahl koerziert werden kann, zu erlauben, überall als Ganzzahl verwendet zu werden, wo Python eine echte Ganzzahl erwartet. Zum Beispiel, wenn __int__ verwendet würde, um ein Objekt in eine Ganzzahl für das Slicing zu konvertieren, dann wären Gleitkommazahlen im Slicing zulässig und x[3.2:5.8] würde keinen Fehler auslösen, wie es sein sollte.

Vorschlag

Füge einen nb_index Slot zu PyNumberMethods und eine entsprechende spezielle Methode __index__ hinzu. Objekte könnten eine Funktion definieren, die in den nb_index Slot gesetzt wird und eine Python-Ganzzahl (entweder eine int oder eine long) zurückgibt. Diese Ganzzahl kann dann entsprechend in einen Py_ssize_t Wert konvertiert werden, wann immer Python einen solchen benötigt, wie z. B. in PySequence_GetSlice, PySequence_SetSlice und PySequence_DelSlice.

Spezifikation

  1. Der nb_index Slot hat die folgende Signatur
    PyObject *index_func (PyObject *self)
    

    Das zurückgegebene Objekt muss ein Python IntType oder Python LongType sein. NULL sollte im Fehlerfall zurückgegeben werden, wobei ein entsprechender Fehler gesetzt ist.

  2. Die spezielle Methode __index__ hat die Signatur
    def __index__(self):
        return obj
    

    wobei obj entweder eine int oder eine long sein muss.

  3. 3 neue abstrakte C-API-Funktionen werden hinzugefügt
    1. Die erste prüft, ob das Objekt den Index-Slot unterstützt und ob er gefüllt ist.
      int PyIndex_Check(obj)
      

      Dies gibt wahr zurück, wenn das Objekt den nb_index Slot definiert.

    2. Die zweite ist ein einfacher Wrapper um den nb_index Aufruf, der PyExc_TypeError auslöst, wenn der Aufruf nicht verfügbar ist oder keine int oder long zurückgibt. Da PyIndex_Check innerhalb des Aufrufs PyNumber_Index durchgeführt wird, kann man ihn direkt aufrufen und Fehler verwalten, anstatt zuerst die Kompatibilität zu prüfen.
      PyObject *PyNumber_Index (PyObject *obj)
      
    3. Der dritte Aufruf hilft bei der häufigen Situation, tatsächlich einen Py_ssize_t Wert aus dem Objekt für Indizierung oder andere Zwecke zu benötigen.
      Py_ssize_t PyNumber_AsSsize_t(PyObject *obj, PyObject *exc)
      

      Die Funktion ruft den nb_index Slot von obj auf, wenn dieser verfügbar ist, und konvertiert dann die zurückgegebene Python-Ganzzahl in einen Py_ssize_t Wert. Wenn dies erfolgreich ist, wird der Wert zurückgegeben. Das zweite Argument ermöglicht die Steuerung dessen, was passiert, wenn die von nb_index zurückgegebene Ganzzahl nicht in einen Py_ssize_t Wert passt.

      Wenn exc NULL ist, dann wird der zurückgegebene Wert auf PY_SSIZE_T_MAX oder PY_SSIZE_T_MIN gekürzt, je nachdem, ob der nb_index Slot von obj eine positive oder negative Ganzzahl zurückgegeben hat. Wenn exc nicht NULL ist, dann ist es das Fehlerobjekt, das gesetzt wird, um den PyExc_OverflowError zu ersetzen, der ausgelöst wurde, als die Python-Ganzzahl oder lange Ganzzahl in Py_ssize_t konvertiert wurde.

  4. Eine neue Funktion operator.index(obj) wird hinzugefügt, die das Äquivalent von obj.__index__() aufruft und einen Fehler auslöst, wenn obj die spezielle Methode nicht implementiert.

Implementierungsplan

  1. Füge den nb_index Slot in object.h hinzu und ändere typeobject.c, um die Methode __index__ zu erstellen.
  2. Ändere das Makro ISINT in ceval.c zu ISINDEX und passe es an, um Objekte mit definiertem Index-Slot zu berücksichtigen.
  3. Ändere die Funktion _PyEval_SliceIndex, um Objekte mit definiertem Index-Slot zu berücksichtigen.
  4. Ändere alle eingebauten Objekte (z. B. Listen), die die as_mapping Slots für den Subskriptzugriff verwenden und eine spezielle Prüfung auf Ganzzahlen durchführen, um auch den Slot zu prüfen.
  5. Füge den nb_index Slot zu Ganzzahlen und langen Ganzzahlen hinzu (die einfach sich selbst zurückgeben).
  6. Füge die C-API PyNumber_Index hinzu, um eine Ganzzahl von jedem Python-Objekt zurückzugeben, das den nb_index Slot hat.
  7. Füge die Funktion operator.index(x) hinzu.
  8. Ändere arrayobject.c und mmapmodule.c, um die neue C-API für ihre Subskription und andere Bedürfnisse zu verwenden.
  9. Füge Unit-Tests hinzu

Diskussionsfragen

Geschwindigkeit

Die Implementierung sollte Python nicht verlangsamen, da Ganzzahlen und lange Ganzzahlen, die als Indizes verwendet werden, in der gleichen Anzahl von Anweisungen abgeschlossen werden. Die einzige Änderung wird sein, dass das, was früher einen Fehler erzeugt hätte, jetzt akzeptabel sein wird.

Warum nicht nb_int verwenden, das bereits vorhanden ist?

Die Methode nb_int wird für die Koerzierung verwendet und hat daher eine grundlegend andere Bedeutung als das, was hier vorgeschlagen wird. Dieser PEP schlägt eine Methode vor, damit etwas, das sich *bereits* wie eine Ganzzahl verhalten kann, diese Information an Python weitergibt, wenn es eine Ganzzahl benötigt. Das größte Beispiel dafür, warum die Verwendung von nb_int schlecht wäre, ist, dass Gleitkommazahlen bereits die Methode nb_int definieren, aber Gleitkommazahlen *nicht* als Indizes in einer Sequenz verwendet werden sollten.

Warum der Name __index__?

Es wurden einige Fragen bezüglich des Namens __index__ aufgeworfen, wenn andere Interpretationen des Slots möglich sind. Zum Beispiel kann der Slot jedes Mal verwendet werden, wenn Python intern eine Ganzzahl benötigt (wie z. B. bei "mystring" * 3). Der Name wurde von Guido vorgeschlagen, da die Slicing-Syntax der wichtigste Grund für die Existenz eines solchen Slots ist und letztendlich kein besserer Name auftauchte. Siehe den Diskussionsfaden [1] für Beispiele von vorgeschlagenen Namen wie "__discrete__" und "__ordinal__".

Warum PyObject * von nb_index zurückgeben?

Anfänglich wurde Py_ssize_t als Rückgabetyp für den nb_index Slot gewählt. Dies führte jedoch zu einer Unfähigkeit, Über- und Unterlauf-Fehler ohne unschöne und fehleranfällige Hacks zu verfolgen und zu unterscheiden. Da der nb_index Slot in mindestens 3 verschiedenen Weisen im Python-Kern verwendet wird (um eine Ganzzahl zu erhalten, um einen Slice-Endpunkt zu erhalten und um einen Sequenzindex zu erhalten), ist eine erhebliche Flexibilität erforderlich, um all diese Fälle zu behandeln. Die Wichtigkeit, die notwendige Flexibilität zur Behandlung aller Anwendungsfälle zu haben, ist entscheidend. Zum Beispiel führte die anfängliche Implementierung, die Py_ssize_t für nb_index zurückgab, zur Entdeckung, dass auf einem 32-Bit-Computer mit >=2 GB RAM s = 'x' * (2**100) funktioniert, aber len(s) auf 2147483647 gekürzt wurde. Mehrere Korrekturen wurden vorgeschlagen, aber schließlich wurde entschieden, dass nb_index ein Python-Objekt zurückgeben muss, ähnlich wie die nb_int und nb_long Slots, um Überläufe korrekt zu behandeln.

Warum kann __index__ kein beliebiges Objekt mit der Methode nb_index zurückgeben?

Dies würde in vielen verschiedenen, schwer zu prüfenden Weisen zu einer unendlichen Rekursion führen. Diese Einschränkung ähnelt der Anforderung, dass __nonzero__ eine Ganzzahl oder einen bool zurückgeben muss.

Referenzimplementierung

Als Patch 1436368 an SourceForge übermittelt.

Referenzen


Quelle: https://github.com/python/peps/blob/main/peps/pep-0357.rst

Zuletzt geändert: 2025-02-01 08:55:40 GMT