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

Python Enhancement Proposals

PEP 436 – Die Argument Clinic DSL

Autor:
Larry Hastings <larry at hastings.org>
Discussions-To:
Python-Dev Liste
Status:
Final
Typ:
Standards Track
Erstellt:
22. Feb 2013
Python-Version:
3.4

Inhaltsverzeichnis

Zusammenfassung

Dieses Dokument schlägt „Argument Clinic“ vor, eine DSL zur Erleichterung der Argumentenverarbeitung für eingebaute Funktionen in der Implementierung von CPython.

Begründung und Ziele

Die primäre Implementierung von Python, „CPython“, ist in einer Mischung aus Python und C geschrieben. Ein Implementierungsdetail von CPython sind sogenannte „eingebaute“ Funktionen – Funktionen, die für Python-Programme verfügbar, aber in C geschrieben sind. Wenn ein Python-Programm eine eingebaute Funktion aufruft und Argumente übergibt, müssen diese Argumente von Python-Werten in C-Werte übersetzt werden. Dieser Prozess wird als „Argumenten-Parsing“ bezeichnet.

Ab CPython 3.3 parsen eingebaute Funktionen ihre Argumente fast immer mit einer von zwei Funktionen: der ursprünglichen PyArg_ParseTuple() [1] und der moderneren PyArg_ParseTupleAndKeywords(). [2] Erstere behandelt nur positionale Parameter; letztere unterstützt auch Schlüsselwort- und nur-Schlüsselwort-Parameter und wird für neuen Code bevorzugt.

Bei beiden Funktionen gibt der Aufrufer die Übersetzung für das Parsen von Argumenten in einem „Formatstring“ an: [3] Jeder Parameter entspricht einer „Format-Einheit“, einer kurzen Zeichenfolge, die der Parsing-Funktion mitteilt, welche Python-Typen akzeptiert werden und wie sie in den entsprechenden C-Wert für diesen Parameter übersetzt werden.

PyArg_ParseTuple() war vernünftig, als es zum ersten Mal konzipiert wurde. Es gab nur etwa ein Dutzend dieser „Format-Einheiten“; jede war deutlich, leicht zu verstehen und zu merken. Aber im Laufe der Jahre wurde die PyArg_Parse-Schnittstelle auf vielfältige Weise erweitert. Die moderne API ist komplex, bis zu einem Punkt, an dem sie etwas schmerzhaft zu verwenden ist. Betrachten Sie

  • Es gibt jetzt vierzig verschiedene „Format-Einheiten“; einige sind sogar drei Zeichen lang. Dies erschwert es dem Programmierer zu verstehen, was der Formatstring aussagt – oder ihn vielleicht sogar zu parsen – ohne ihn ständig mit der Dokumentation abzugleichen.
  • Es gibt auch sechs Meta-Format-Einheiten, die im Formatstring vergraben sein können. (Sie sind: "()|$:;".)
  • Je mehr Format-Einheiten hinzugefügt werden, desto unwahrscheinlicher ist es, dass der Implementierer eine einfach zu merkende Mnemotechnik für die Format-Einheit wählen kann, da das Zeichen der Wahl wahrscheinlich bereits in Gebrauch ist. Mit anderen Worten, je mehr Format-Einheiten wir haben, desto obskurer werden die Format-Einheiten.
  • Mehrere Format-Einheiten sind fast identisch mit anderen und weisen nur subtile Unterschiede auf. Dies macht das Verständnis der genauen Semantik des Formatstrings noch schwieriger und kann es schwierig machen, genau die gewünschte Format-Einheit herauszufinden.
  • Der Docstring wird als statischer C-String angegeben, was das Lesen und Bearbeiten geringfügig mühsam macht, da er den Regeln für C-String-Anführungszeichen gehorchen muss.
  • Beim Hinzufügen eines neuen Parameters zu einer Funktion, die PyArg_ParseTupleAndKeywords() verwendet, müssen sechs verschiedene Stellen im Code geändert werden: [4]
    • Deklaration der Variable zur Speicherung des Arguments.
    • Übergabe eines Zeigers auf diese Variable an der richtigen Stelle in PyArg_ParseTupleAndKeywords(), wobei auch eventuelle „Längen“- oder „Konverter“-Argumente in der richtigen Reihenfolge übergeben werden.
    • Hinzufügen des Namens des Arguments an der richtigen Stelle des an PyArg_ParseTupleAndKeywords() übergebenen „Keywords“-Arrays.
    • Hinzufügen der Format-Einheit an der richtigen Stelle des Formatstrings.
    • Hinzufügen des Parameters zum Prototyp im Docstring.
    • Dokumentation des Parameters im Docstring.
  • Es gibt derzeit keinen Mechanismus, mit dem eingebaute Funktionen ihre „Signatur“-Informationen bereitstellen können (siehe inspect.getfullargspec und inspect.Signature). Das Hinzufügen dieser Informationen mit einem Mechanismus, der den bestehenden PyArg_Parse-Funktionen ähnelt, würde eine weitere Wiederholung erfordern.

Das Ziel von Argument Clinic ist es, diese API durch einen Mechanismus zu ersetzen, der keine dieser Nachteile erbt.

  • Sie müssen jeden Parameter nur einmal angeben.
  • Alle Informationen zu einem Parameter sind an einem Ort zusammengefasst.
  • Für jeden Parameter geben Sie eine Konvertierungsfunktion an; Argument Clinic kümmert sich um die Übersetzung von Python-Werten in C-Werte für Sie.
  • Argument Clinic ermöglicht auch eine Feinabstimmung des Verhaltens der Argumentenverarbeitung mit parametrisierten Konvertierungsfunktionen.
  • Docstrings werden in einfachem Text verfasst. Funktions-Docstrings sind erforderlich; Docstrings pro Parameter werden empfohlen.
  • Von hier aus generiert Argument Clinic für Sie allen mühsamen, sich wiederholenden Code und die Datenstrukturen, die CPython intern benötigt. Sobald Sie die Schnittstelle definiert haben, besteht der nächste Schritt darin, Ihre Implementierung mit nativen C-Typen zu schreiben. Jedes Detail des Argumenten-Parsings wird für Sie erledigt.

Argument Clinic ist als Präprozessor implementiert. Es schöpft Inspiration für seinen Workflow direkt aus [Cog] von Ned Batchelder. Um Clinic zu verwenden, fügen Sie einen Blockkommentar in Ihren C-Quellcode ein, der mit speziellen Textzeichenfolgen beginnt und endet, und führen Sie dann Clinic für die Datei aus. Clinic findet den Blockkommentar, verarbeitet dessen Inhalt und schreibt die Ausgabe direkt nach dem Kommentar zurück in Ihre C-Quelldatei. Die Absicht ist, dass die Ausgabe von Clinic Teil Ihres Quellcodes wird; sie wird in die Versionskontrolle eingecheckt und mit Quellcode-Paketen verteilt. Das bedeutet, dass Python weiterhin gebrauchsfertig ausgeliefert wird. Es erschwert die Entwicklung geringfügig; um eine neue Funktion hinzuzufügen oder die Argumente oder Dokumentation einer vorhandenen Funktion mit Clinic zu ändern, benötigen Sie einen funktionierenden Python-3-Interpreter.

Zukünftige Ziele von Argument Clinic umfassen

  • die Bereitstellung von Signaturinformationen für Built-ins,
  • die Ermöglichung für alternative Implementierungen von Python, automatisierte Kompatibilitätstests für Bibliotheken zu erstellen, und
  • die Beschleunigung der Argumentenverarbeitung durch Verbesserungen am generierten Code.

DSL-Syntax-Zusammenfassung

Die Argument Clinic DSL wird als Kommentar definiert, der in eine C-Datei eingebettet ist, wie folgt. Die Spalte „Beispiel“ rechts zeigt Ihnen die Eingabe für die Argument Clinic DSL, und die Spalte „Abschnitt“ links gibt an, was jede Zeile der Reihe nach darstellt.

Die DSL-Syntax von Argument Clinic spiegelt die Python def-Anweisung wider und verleiht ihr daher eine gewisse Vertrautheit für Python-Core-Entwickler.

+-----------------------+-----------------------------------------------------------------+
| Section               | Example                                                         |
+-----------------------+-----------------------------------------------------------------+
| Clinic DSL start      | /*[clinic]                                                      |
| Module declaration    | module module_name                                              |
| Class declaration     | class module_name.class_name                                    |
| Function declaration  | module_name.function_name  -> return_annotation                 |
| Parameter declaration |       name : converter(param=value)                             |
| Parameter docstring   |           Lorem ipsum dolor sit amet, consectetur               |
|                       |           adipisicing elit, sed do eiusmod tempor               |
| Function docstring    | Lorem ipsum dolor sit amet, consectetur adipisicing             |
|                       | elit, sed do eiusmod tempor incididunt ut labore et             |
| Clinic DSL end        | [clinic]*/                                                      |
| Clinic output         | ...                                                             |
| Clinic output end     | /*[clinic end output:<checksum>]*/                              |
+-----------------------+-----------------------------------------------------------------+

Um einen Eindruck von der vorgeschlagenen DSL-Syntax zu vermitteln, hier einige Beispiel-Clinic-Codeblöcke. Dieser erste Block spiegelt den normalerweise bevorzugten Stil wider, einschließlich Leerzeilen zwischen den Parametern und Docstrings pro Argument. Er enthält auch einen lokal erstellten, benutzerdefinierten Konverter (path_t).

/*[clinic]
os.stat as os_stat_fn -> stat result

   path: path_t(allow_fd=1)
       Path to be examined; can be string, bytes, or open-file-descriptor int.

   *

   dir_fd: OS_STAT_DIR_FD_CONVERTER = DEFAULT_DIR_FD
       If not None, it should be a file descriptor open to a directory,
       and path should be a relative string; path will then be relative to
       that directory.

   follow_symlinks: bool = True
       If False, and the last element of the path is a symbolic link,
       stat will examine the symbolic link itself instead of the file
       the link points to.

Perform a stat system call on the given path.

{parameters}

dir_fd and follow_symlinks may not be implemented
  on your platform.  If they are unavailable, using them will raise a
  NotImplementedError.

It's an error to use dir_fd or follow_symlinks when specifying path as
  an open file descriptor.

[clinic]*/

Dieses zweite Beispiel zeigt einen minimalen Clinic-Codeblock, der alle Parameter-Docstrings und nicht-signifikanten Leerzeilen weglässt.

/*[clinic]
os.access
   path: path
   mode: int
   *
   dir_fd: OS_ACCESS_DIR_FD_CONVERTER = 1
   effective_ids: bool = False
   follow_symlinks: bool = True
Use the real uid/gid to test for access to a path.
Returns True if granted, False otherwise.

{parameters}

dir_fd, effective_ids, and follow_symlinks may not be implemented
  on your platform.  If they are unavailable, using them will raise a
  NotImplementedError.

Note that most operations will use the effective uid/gid, therefore this
  routine can be used in a suid/sgid environment to test if the invoking user
  has the specified access to the path.

[clinic]*/

Dieses letzte Beispiel zeigt einen Clinic-Codeblock, der Gruppen optionaler Parameter behandelt, einschließlich der Parameter auf der linken Seite.

/*[clinic]
curses.window.addch

   [
   y: int
     Y-coordinate.

   x: int
     X-coordinate.
   ]

   ch: char
     Character to add.

   [
   attr: long
     Attributes for the character.
   ]

   /

Paint character ch at (y, x) with attributes attr,
overwriting any character previously painter at that location.
By default, the character position and attributes are the
current settings for the window object.
[clinic]*/

Allgemeines Verhalten der Argument Clinic DSL

Alle Zeilen unterstützen # als Zeilenkommentar-Trennzeichen *außer* Docstrings. Leerzeilen werden immer ignoriert.

Wie Python selbst ist führender Leerraum in der Argument Clinic DSL signifikant. Die erste Zeile des „Funktions“-Abschnitts ist die Funktionsdeklaration. Eingerückte Zeilen unter der Funktionsdeklaration deklarieren Parameter, eine pro Zeile; Zeilen darunter, die noch weiter eingerückt sind, sind Docstrings pro Parameter. Schließlich beenden die erste Zeile, die zurück auf Spalte 0 eingerückt ist, die Parameterdeklarationen und beginnen den Funktions-Docstring.

Parameter-Docstrings sind optional; Funktions-Docstrings sind es nicht. Funktionen, die keine Argumente angeben, können einfach die Funktionsdeklaration gefolgt vom Docstring angeben.

Modul- und Klassendeklarationen

Wenn eine C-Datei ein Modul oder eine Klasse implementiert, sollte dies Clinic mitgeteilt werden. Die Syntax ist einfach.

module module_name

oder

class module_name.class_name

(Beachten Sie, dass dies keine speziellen Syntaxelemente sind; sie werden als Direktiven implementiert.)

Der Modulname oder Klassenname sollte immer der vollständige, punktierte Pfad vom Top-Level-Modul sein. Verschachtelte Module und Klassen werden unterstützt.

Funktionsdeklaration

Die vollständige Form der Funktionsdeklaration lautet wie folgt.

dotted.name [ as legal_c_id ] [ -> return_annotation ]

Der punktierte Name sollte der vollständige Name der Funktion sein, beginnend mit dem Paket der höchsten Ebene (z. B. „os.stat“ oder „curses.window.addch“).

Die Syntax „as legal_c_id“ ist optional. Argument Clinic verwendet den Namen der Funktion, um die Namen der generierten C-Funktionen zu erstellen. Unter bestimmten Umständen kann der generierte Name mit anderen globalen Namen im Namensraum des C-Programms kollidieren. Die Syntax „as legal_c_id“ erlaubt es Ihnen, den generierten Namen durch Ihren eigenen zu überschreiben; ersetzen Sie „legal_c_id“ durch einen beliebigen gültigen C-Bezeichner. Wenn übersprungen, muss das Schlüsselwort „as“ ebenfalls weggelassen werden.

Die Rückgabenotiz ist ebenfalls optional. Wenn sie weggelassen wird, muss auch der Pfeil („->“) weggelassen werden. Wenn sie angegeben wird, muss der Wert für die Rückgabenotiz mit ast.literal_eval kompatibel sein, und er wird als *Rückgabekonverter* interpretiert.

Parameterdeklaration

Die vollständige Form der Parameterdeklarationszeile lautet wie folgt.

name: converter [ (parameter=value [, parameter2=value2]) ] [ = default]

Der „Name“ muss ein gültiger C-Bezeichner sein. Leerraum ist zwischen dem Namen und dem Doppelpunkt zulässig (obwohl dies nicht der bevorzugte Stil ist). Leerraum ist zwischen dem Doppelpunkt und dem Konverter zulässig (und erwünscht).

Der „Konverter“ ist der Name einer der bei Argument Clinic registrierten „Konverterfunktionen“. Clinic wird mit einer Reihe von integrierten Konvertern geliefert; neue Konverter können auch dynamisch hinzugefügt werden. Bei der Auswahl eines Konverters schränken Sie automatisch ein, welche Python-Typen als Eingabe zulässig sind, und geben an, welchen Typ die Ausgabevariable (oder Variablen) haben wird. Obwohl viele der Konverter den Namen von C-Typen oder vielleicht Python-Typen ähneln, kann der Name eines Konverters jeder gültige Python-Bezeichner sein.

Wenn der Konverter von Klammern gefolgt wird, umschließen diese Parameter des Konverters. Die Syntax spiegelt die Übergabe von Argumenten an einen Python-Funktionsaufruf wider: Der Parameter muss immer benannt sein, als ob es sich um „nur-Schlüsselwort-Parameter“ handeln würde, und die für die Parameter bereitgestellten Werte ähneln syntaktisch Python-Literalwerten. Diese Parameter sind immer optional, sodass alle Konvertierungsfunktionen ohne Parameter aufgerufen werden können. In diesem Fall können Sie die Klammern auch ganz weglassen; dies ist immer äquivalent zur Angabe leerer Klammern. Die für diese Parameter bereitgestellten Werte müssen mit ast.literal_eval kompatibel sein.

Der „Standardwert“ ist ein Python-Literalwert. Standardwerte sind optional; wenn sie nicht angegeben werden, muss auch das Gleichheitszeichen weggelassen werden. Parameter, die keinen Standardwert haben, sind implizit erforderlich. Der Standardwert wird dynamisch zugewiesen, „live“ im generierten C-Code, und obwohl er als Python-Wert angegeben wird, wird er im generierten C-Code in einen nativen C-Wert übersetzt. Wenige Standardwerte sind aufgrund dieses manuellen Übersetzungsschritts zulässig.

Wäre dies eine Python-Funktionsdeklaration, wäre eine Parameterdeklaration durch ein nachfolgendes Komma oder eine schließende Klammer abgegrenzt. Argument Clinic verwendet jedoch keines davon; Parameterdeklarationen werden durch einen Zeilenumbruch abgegrenzt. Ein nachfolgendes Komma oder eine schließende Klammer sind nicht zulässig.

Die erste Parameterdeklaration legt den Einzug für alle Parameterdeklarationen in einem bestimmten Clinic-Codeblock fest. Alle nachfolgenden Parameter müssen auf derselben Ebene eingerückt sein.

Legacy-Konverter

Zur Vereinfachung bei der Konvertierung vorhandenen Codes zu Argument Clinic bietet Clinic eine Reihe von Legacy-Konvertern, die den PyArg_ParseTuple-Format-Einheiten entsprechen. Sie werden als C-String angegeben, der die Format-Einheit enthält. Um beispielsweise einen Parameter „foo“ als Integer zu spezifizieren, der einen Python-„int“ annimmt und einen C-int ausgibt, könnten Sie angeben

foo : "i"

(Um einem C-String näher zu kommen, müssen diese immer doppelte Anführungszeichen verwenden.)

Obwohl diese den PyArg_ParseTuple-Format-Einheiten ähneln, gibt es keine Garantie, dass die Implementierung eine PyArg_Parse-Funktion zum Parsen aufruft.

Diese Syntax unterstützt keine Parameter. Daher unterstützt sie keine der Format-Einheiten, die Eingabeparameter erfordern („O!“, „O&“, „es“, „es#“, „et“, „et#“). Parameter, die eine dieser Konvertierungen erfordern, können die Legacy-Syntax nicht verwenden. (Sie können jedoch immer noch einen Standardwert angeben.)

Parameter-Docstrings

Alle Zeilen, die darunter erscheinen und weiter eingerückt sind als eine Parameterdeklaration, sind der Docstring für diesen Parameter. Alle solchen Zeilen werden „dedented“, bis die erste Zeile linksbündig ist.

Spezielle Syntax für Parameterzeilen

Es gibt vier spezielle Symbole, die im Parameterabschnitt verwendet werden können. Jedes dieser Symbole muss in einer eigenen Zeile stehen, die auf derselben Ebene wie die Parameterdeklarationen eingerückt ist. Die vier Symbole sind

*
Legt fest, dass alle nachfolgenden Parameter ausschließlich als Schlüsselwörter behandelt werden.
[
Legt den Beginn einer optionalen „Gruppe“ von Parametern fest. Beachten Sie, dass „Gruppen“ innerhalb anderer „Gruppen“ verschachtelt sein können. Siehe Funktionen mit ausschließlich positionalem Parametern unten. Beachten Sie, dass `[` derzeit nur für Funktionen zulässig ist, bei denen *alle* Parameter als ausschließlich positional markiert sind, siehe ` / ` unten.
]
Beendet eine optionale „Gruppe“ von Parametern.
/
Legt fest, dass alle *vorhergehenden* Argumente ausschließlich positional sind. Derzeit unterstützt Argument Clinic keine Funktionen mit sowohl ausschließlich positionalem als auch nicht-ausschließlich-positionalem Argumenten. Daher: Wenn ` / ` für eine Funktion angegeben wird, muss es derzeit immer nach dem *letzten* Parameter erfolgen. Außerdem unterstützt Argument Clinic derzeit keine Standardwerte für ausschließlich positionale Parameter.

(Die Semantik von ` / ` folgt einer Syntax für ausschließlich positionale Parameter in Python, die einst von Guido vorgeschlagen wurde. [5])

Funktions-Docstring

Die erste Zeile ohne führenden Leerraum nach der Funktionsdeklaration ist die erste Zeile des Funktions-Docstrings. Alle nachfolgenden Zeilen des Clinic-Blocks werden als Teil des Docstrings betrachtet und ihr führender Leerraum wird beibehalten.

Wenn die Zeichenfolge {parameters} in einer eigenen Zeile innerhalb des Funktions-Docstrings erscheint, fügt Argument Clinic eine Liste aller Parameter ein, die Docstrings haben, wobei jeder solche Parameter von seinem Docstring gefolgt wird. Der Name des Parameters steht in einer eigenen Zeile; der Docstring beginnt in einer nachfolgenden Zeile, und alle Zeilen des Docstrings werden um zwei Leerzeichen eingerückt. (Parameter ohne Parameter-Docstring werden unterdrückt.) Die gesamte Liste wird um den führenden Leerraum eingerückt, der vor dem {parameters}-Token stand.

Wenn die Zeichenfolge {parameters} nicht im Docstring erscheint, hängt Argument Clinic eine an das Ende des Docstrings an, fügt davor eine Leerzeile ein, wenn der Docstring nicht mit einer Leerzeile endet, und die Parameterliste steht in Spalte 0.

Konverter

Argument Clinic enthält ein vorinitialisiertes Register von Konverterfunktionen. Beispielhafte Konverterfunktionen

int
Akzeptiert ein Python-Objekt, das `__int__` implementiert; gibt ein C `int` aus.
byte
Akzeptiert ein Python-Integer; gibt ein `unsigned char` aus. Der Integer muss im Bereich [0, 256) liegen.
str
Akzeptiert ein Python-String-Objekt; gibt einen C `char *` aus. Kodiert den String automatisch mit dem `ascii`-Codec.
PyObject
Akzeptiert jedes Objekt; gibt ein C `PyObject *` ohne Konvertierung aus.

Alle Konverter akzeptieren die folgenden Parameter

doc_default
Der Python-Wert, der anstelle des tatsächlichen Standardwerts des Parameters in Python-Kontexten verwendet wird. Anders ausgedrückt: Wenn angegeben, wird dieser Wert für den Standardwert des Parameters im Docstring und in der `Signature` verwendet. (TBD alternative Semantik: Wenn die Zeichenfolge ein gültiger Python-Ausdruck ist, der mit `eval()` in einen Python-Wert gerendert werden kann, dann wird das Ergebnis von `eval()` darauf als Standardwert in der `Signature` verwendet.) Ignoriert, wenn kein Standardwert vorhanden ist.
required
Normalerweise ist jeder Parameter, der einen Standardwert hat, automatisch optional. Ein Parameter, bei dem „required“ auf true gesetzt ist, wird als erforderlich (nicht optional) betrachtet, auch wenn er einen Standardwert hat. Die generierte Dokumentation zeigt auch keinen Standardwert an.

Zusätzlich können Konverter eine oder mehrere der folgenden optionalen Parameter individuell akzeptieren

annotation
Gibt explizit die Anmerkung pro Parameter für diesen Parameter an. Normalerweise ist die Konvertierungsfunktion für die Generierung der Anmerkung (falls vorhanden) verantwortlich.
bitwise
Für Konverter, die vorzeichenlose Ganzzahlen akzeptieren. Wenn die übergebene Python-Ganzzahl vorzeichenbehaftet ist, kopieren Sie die Bits direkt, auch wenn sie negativ ist.
encoding
Für Konverter, die Zeichenketten akzeptieren. Zu verwendende Kodierung beim Kodieren einer Unicode-Zeichenkette in einen `char *`.
immutable
Nur unveränderliche Werte akzeptieren.
length
Für Konverter, die iterierbare Typen akzeptieren. Fordert, dass der Konverter auch die Länge des Iterables ausgibt, übergeben an die `_impl`-Funktion in einer `Py_ssize_t`-Variable; ihr Name wird der Name dieses Parameters sein, der mit „_length“ angehängt ist.
nullable
Dieser Konverter akzeptiert normalerweise kein `None`, aber in diesem Fall sollte er es tun. Wenn `None` auf der Python-Seite übergeben wird, ist das entsprechende C-Argument `NULL`. (Das von diesem Konverter ausgegebene `_impl`-Argument ist vermutlich ein Zeigertyp.)
types
Eine Liste von Zeichenketten, die die akzeptablen Python-Typen für dieses Objekt darstellen. Es gibt auch vier Zeichenketten, die Python-Protokolle darstellen
  • „buffer“
  • „mapping“
  • „number“
  • „sequence“
zeroes
Für Konverter, die Zeichentyper akzeptieren. Der konvertierte Wert sollte eingebettete Nullen enthalten dürfen.

Rückgabekonverter

Ein *Rückgabekonverter* führt konzeptionell die umgekehrte Operation eines Konverters aus: Er konvertiert einen nativen C-Wert in seinen äquivalenten Python-Wert.

Direktiven

Argument Clinic erlaubt auch „Direktiven“ in Clinic-Codeblöcken. Direktiven ähneln „Pragmas“ in C; es sind Anweisungen, die das Verhalten von Argument Clinic ändern.

Das Format einer Direktive ist wie folgt.

directive_name [argument [second_argument [ ... ]]]

Direktiven nehmen nur Positionsargumente an.

Ein Clinic-Codeblock muss entweder eine oder mehrere Direktiven oder eine Funktionsdeklaration enthalten. Er kann beides enthalten, in diesem Fall müssen alle Direktiven vor der Funktionsdeklaration stehen.

Intern werden Direktiven direkt Python-Aufrufbaren zugeordnet. Die Argumente der Direktive werden direkt an die Aufrufbare als Positionsargumente vom Typ `str()` übergeben.

Beispielhafte mögliche Direktiven umfassen die Erzeugung, Unterdrückung oder Umleitung der Clinic-Ausgabe. Auch die Schlüsselwörter „module“ und „class“ werden in der Prototype als Direktiven implementiert.

Python-Code

Argument Clinic erlaubt auch das Einbetten von Python-Code in C-Dateien, der bei der Verarbeitung der Datei durch Argument Clinic direkt ausgeführt wird. Eingebetteter Code sieht so aus.

/*[python]

# this is python code!
print("/" + "* Hello world! *" + "/")

[python]*/
/* Hello world! */
/*[python end:da39a3ee5e6b4b0d3255bfef95601890afd80709]*/

Die Zeile `/* Hello world! */` oben wurde durch Ausführen des Python-Codes im vorhergehenden Kommentar generiert.

Jeder Python-Code ist gültig. Python-Codeabschnitte in Argument Clinic können auch verwendet werden, um direkt mit Clinic zu interagieren; siehe Programmatische Schnittstellen der Argument Clinic.

Ausgabe

Argument Clinic schreibt seine Ausgabe inline in die C-Datei, unmittelbar nach dem Abschnitt des Clinic-Codes. Für „python“-Abschnitte ist die Ausgabe alles, was mit `builtins.print` ausgegeben wird. Für „clinic“-Abschnitte ist die Ausgabe gültiger C-Code, einschließlich

  • eines `#define`, das die korrekte `methoddef`-Struktur für die Funktion bereitstellt.
  • eines Prototyps für die „impl“-Funktion – dies ist, was Sie schreiben werden, um diese Funktion zu implementieren.
  • einer Funktion, die die gesamte Argumentenverarbeitung übernimmt und Ihre „impl“-Funktion aufruft.
  • der Definitionszeile der „impl“-Funktion.
  • und einem Kommentar, der das Ende der Ausgabe anzeigt.

Die Absicht ist, dass Sie den Körper Ihrer Impl-Funktion unmittelbar nach der Ausgabe schreiben – das heißt, Sie schreiben eine öffnende geschweifte Klammer unmittelbar nach dem End-of-Output-Kommentar und implementieren die builtin-Funktion dort im Körper. (Es ist anfangs etwas seltsam, aber seltsam praktisch.)

Argument Clinic definiert die Parameter der Impl-Funktion für Sie. Die Funktion nimmt den ursprünglich übergebenen „self“-Parameter, alle von Ihnen definierten Parameter und möglicherweise einige zusätzliche generierte Parameter („Längen“-Parameter; auch „Gruppen“-Parameter, siehe nächster Abschnitt) entgegen.

Argument Clinic schreibt auch eine Prüfsumme für den Ausgabeabschnitt. Dies ist eine wertvolle Sicherheitsfunktion: Wenn Sie die Ausgabe von Hand ändern, wird Clinic feststellen, dass die Prüfsumme nicht übereinstimmt, und sich weigern, die Datei zu überschreiben. (Sie können Clinic zwingen, mit dem Befehlszeilenargument „-f“ zu überschreiben; Clinic wird die Prüfsummen auch ignorieren, wenn das Befehlszeilenargument „-o“ verwendet wird.)

Schließlich kann Argument Clinic auch die Boilerplate-Definition des PyMethodDef-Arrays für die definierten Klassen und Module ausgeben.

Funktionen mit ausschließlich positionalen Parametern

Ein erheblicher Teil der in C implementierten Python-Builtins verwendet die ältere API für ausschließlich positionale Argumente (`PyArg_ParseTuple()`). In einigen Fällen parsen diese Built-ins ihre Argumente unterschiedlich, je nachdem, wie viele Argumente übergeben wurden. Dies kann eine verwirrende Flexibilität bieten: Es kann Gruppen optionaler Parameter geben, die entweder alle angegeben oder keine angegeben werden müssen. Und gelegentlich befinden sich diese Gruppen auf der *linken* Seite! (Ein repräsentatives Beispiel: `curses.window.addch()`.)

Argument Clinic unterstützt diese Legacy-Anwendungsfälle, indem es Ihnen erlaubt, Parameter in Gruppen anzugeben. Jede optionale Gruppe von Parametern wird mit eckigen Klammern markiert. Beachten Sie, dass diese Gruppen rechts *oder links* von erforderlichen Parametern zulässig sind!

Die von Clinic generierte Impl-Funktion fügt für jede Gruppe einen zusätzlichen Parameter hinzu: „int group_{left|right}_<x>“, wobei x eine monoton steigende Zahl ist, die jeder Gruppe zugewiesen wird, während sie sich von den erforderlichen Argumenten wegbewegt. Dieses Argument ist nicht Null, wenn die Gruppe bei diesem Aufruf angegeben wurde, und Null, wenn sie nicht angegeben wurde.

Beachten Sie, dass Sie in diesem Modus keine Standardargumente angeben können.

Beachten Sie auch, dass es möglich ist, einer Funktion eine Reihe von Gruppen zuzuweisen, sodass es mehrere gültige Zuordnungen von der Anzahl der Argumente zu einer gültigen Menge von Gruppen gibt. Wenn dies geschieht, bricht Clinic mit einer Fehlermeldung ab. Dies sollte kein Problem darstellen, da der ausschließlich positionale Betrieb nur für Legacy-Anwendungsfälle vorgesehen ist und alle Legacy-Funktionen, die dieses eigenartige Verhalten verwenden, eindeutige Zuordnungen haben.

Aktueller Status

Zum Zeitpunkt des Schreibens gibt es eine funktionierende Prototypimplementierung von Argument Clinic online (obwohl die Syntax veraltet sein kann, wenn Sie dies lesen). [6] Der Prototyp generiert Code unter Verwendung der bestehenden `PyArg_Parse`-APIs. Er unterstützt die Übersetzung in alle aktuellen Format-Einheiten außer dem mysteriösen `w*`. Beispielhafte Funktionen, die Argument Clinic verwenden, decken alle Hauptmerkmale ab, einschließlich der ausschliesslich-positionalen Argumentenverarbeitung.

Programmatische Schnittstellen der Argument Clinic

Der Prototyp bietet derzeit auch einen experimentellen Erweiterungsmechanismus, der die Unterstützung für neue Typen „on-the-fly“ hinzufügt. Siehe `Modules/posixmodule.c` im Prototyp für ein Beispiel seiner Verwendung.

In Zukunft wird erwartet, dass Argument Clinic so automatisierbar sein wird, dass Funktionsdeklarationen über Python-Code abgefragt, modifiziert oder komplett neu erstellt werden können. Es könnte sogar das dynamische Hinzufügen Ihrer eigenen benutzerdefinierten DSL ermöglichen!

Anmerkungen / TBD

  • Die API für die Bereitstellung von inspect.Signature-Metadaten für Built-ins wird derzeit diskutiert. Argument Clinic wird die Unterstützung für den Prototyp hinzufügen, wenn dieser praktikabel wird.
  • Alyssa Coghlan schlägt vor, dass wir a) maximal eine links-optionale Gruppe pro Funktion unterstützen und b) im Zweifelsfall die linke Gruppe der rechten Gruppe vorziehen. Dies würde alle unsere bestehenden Anwendungsfälle, einschließlich range(), lösen.
  • Optimalerweise möchten wir, dass Argument Clinic automatisch als Teil des normalen Python-Build-Prozesses ausgeführt wird. Dies birgt jedoch ein Bootstrapping-Problem; wenn Sie kein System-Python 3 haben, benötigen Sie eine Python-3-ausführbare Datei, um Python 3 zu kompilieren. Ich bin sicher, dass dies ein lösbares Problem ist, aber ich weiß nicht, wie die beste Lösung aussehen könnte. (Die Unterstützung dafür erfordert auch eine parallele Lösung für Windows.)
  • In einem ähnlichen Zusammenhang: inspect.Signature hat keine Möglichkeit, Argumentenblöcke darzustellen, wie den links-optionalen Block von `y` und `x` für `curses.window.addch`. Wie weit werden wir gehen, um dieses zugegebenermaßen abweichende Parameterparadigma zu unterstützen?
  • Während des PyCon US 2013 Language Summit wurde diskutiert, dass Argument Clinic auch die eigentliche Dokumentation (in ReST, verarbeitet von Sphinx) für die Funktion generieren soll. Die Logistik hierfür ist TBD, aber es würde erfordern, dass die Docstrings in ReST geschrieben werden, und dass Python einen ReST -> ASCII-Konverter mitliefert. Es wäre am besten, eine Entscheidung darüber zu treffen, bevor wir mit der groß angelegten Konvertierung des CPython-Quellcodes zur Verwendung von Clinic beginnen.
  • Guido schlug vor, dass der „Funktions-Docstring“ von Hand inline, inmitten der Ausgabe geschrieben wird, etwa so.
    /*[clinic]
      ... prototype and parameters (including parameter docstrings) go here
    [clinic]*/
    ... some output ...
    /*[clinic docstring start]*/
    ... hand-edited function docstring goes here   <-- you edit this by hand!
    /*[clinic docstring end]*/
    ... more output
    /*[clinic output end]*/
    

    Ich habe es auf diese Weise versucht und mag es nicht – ich finde es umständlich. Ich bevorzuge es, dass alles, was Sie schreiben, an einer Stelle steht, anstatt einen Teil handbearbeiteter Inhalte inmitten der DSL-Ausgabe zu haben.

  • Argument Clinic unterstützt kein automatisches Tupel-Entpacken (den „(OOO)“-Stil Formatstring für `PyArg_ParseTuple()`.)
  • Das Argument Clinic entfernt etwas Dynamik / Flexibilität. Mit PyArg_ParseTuple() konnte man theoretisch zur Laufzeit unterschiedliche Kodierungen für die Format-Einheiten „es“/„et“ übergeben. Soweit ich weiß, macht CPython dies selbst nicht, aber es ist möglich, dass externe Benutzer dies tun. (Trivia: Es gibt keine Verwendungen von „es“, die von regrtest ausgeführt werden, und alle Verwendungen von „et“, die ausgeführt werden, befinden sich in socketmodule.c, mit Ausnahme einer in _ssl.c. Sie sind alle statisch und geben die Kodierung "idna" an.)

Danksagungen

Der PEP-Autor möchte Ned Batchelder dafür danken, dass er ihm die Erlaubnis gegeben hat, sein cleveres Design für Cog – „mein Lieblingstool, das ich nie benutzen konnte“ – schamlos zu kopieren. Vielen Dank auch an alle, die Feedback zu der [Bugtracker-Ausgabe] und zu python-dev gegeben haben. Besonderer Dank gilt Alyssa (Nick) Coghlan und Guido van Rossum für ein mitreißendes zweistündiges persönliches Deep-Dive-Gespräch zu diesem Thema auf der PyCon US 2013.

Referenzen


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

Zuletzt geändert: 2025-02-01 08:59:27 GMT