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

Python Enhancement Proposals

PEP 741 – Python Konfigurations-C-API

Autor:
Victor Stinner <vstinner at python.org>
Discussions-To:
Discourse thread
Status:
Final
Typ:
Standards Track
Erstellt:
18-Jan-2024
Python-Version:
3.14
Post-History:
19-Jan-2024, 08-Feb-2024
Resolution:
Discourse-Nachricht

Inhaltsverzeichnis

Zusammenfassung

Eine C-API hinzufügen, um die Python-Initialisierung zu konfigurieren, ohne sich auf C-Strukturen zu verlassen, und die Möglichkeit, ABI-kompatible Änderungen in Zukunft vorzunehmen.

Vervollständigen Sie die PEP 587 API durch Hinzufügen von PyInitConfig_AddModule(), die verwendet werden kann, um ein integriertes Erweiterungsmodul hinzuzufügen; Feature, das zuvor als "inittab" bezeichnet wurde.

Fügen Sie die Funktionen PyConfig_Get() und PyConfig_Set() hinzu, um die aktuelle Laufzeitkonfiguration abzurufen und festzulegen.

PEP 587 "Python Initialization Configuration" hat alle Wege zur Konfiguration der Python-Initialisierung vereinheitlicht. Diese PEP vereinheitlicht auch die Konfiguration der Python-Vorinitialisierung und der Python-Initialisierung in einer einzigen API. Darüber hinaus bietet diese PEP nur eine einzige Wahl für die Einbettung von Python anstelle von zwei "Python"- und "Isolated"-Optionen (PEP 587), um die API weiter zu vereinfachen.

Die Low-Level PEP 587 PyConfig API bleibt für Anwendungsfälle mit einer bewusst höheren Kopplung an CPython-Implementierungsdetails (wie die Emulation der vollen Funktionalität von CPython's CLI, einschließlich seiner Konfigurationsmechanismen) verfügbar.

Begründung

Die Laufzeitkonfiguration abrufen

PEP 587 bietet keine API, um die aktuelle Laufzeitkonfiguration zu ermitteln, sondern nur, um die Python-Initialisierung zu konfigurieren.

Zum Beispiel wurde die globale Konfigurationsvariable Py_UnbufferedStdioFlag in Python 3.12 als veraltet markiert und die Verwendung von PyConfig.buffered_stdio wird stattdessen empfohlen. Sie funktioniert nur zur Konfiguration von Python, es gibt keine öffentliche API, um PyConfig.buffered_stdio abzurufen.

Benutzer der begrenzten C-API fordern eine öffentliche API zum Abrufen der aktuellen Laufzeitkonfiguration an.

Cython muss die Konfigurationsoption optimization_level abrufen: issue.

Als globale Konfigurationsvariablen im Jahr 2022 als veraltet markiert wurden, forderte Marc-André Lemburg eine C-API an, um auf diese Konfigurationsvariablen zur Laufzeit (nicht nur während der Python-Initialisierung) zugreifen zu können.

Sicherheitskorrektur

Um CVE-2020-10735, eine Denial-of-Service-Attacke bei der Konvertierung einer sehr großen Zeichenkette in eine Ganzzahl (in Basis 10), zu beheben, wurde diskutiert, einen neuen PyConfig Member zu stabilen Zweigen hinzuzufügen, was die ABI beeinträchtigt.

Gregory P. Smith schlug eine andere API vor, die eine textbasierte Konfigurationsdatei verwendet, um nicht durch PyConfig Member eingeschränkt zu sein: FR: Allow private runtime config to enable extending without breaking the PyConfig ABI (August 2022).

Schließlich wurde entschieden, keinen neuen PyConfig Member zu stabilen Zweigen hinzuzufügen, sondern nur einen neuen PyConfig.int_max_str_digits Member zum Entwicklungszweig (der zu Python 3.12 wurde). Eine dedizierte private globale Variable (unabhängig von PyConfig) wird in stabilen Zweigen verwendet.

Redundanz zwischen PyPreConfig und PyConfig

Die Python-Vorinitialisierung verwendet die PyPreConfig Struktur und die Python-Initialisierung verwendet die PyConfig Struktur. Beide Strukturen haben vier doppelte Member: dev_mode, parse_argv, isolated und use_environment.

Die Redundanz entsteht dadurch, dass die beiden Strukturen getrennt sind, während einige PyConfig Member für die Vorinitialisierung benötigt werden.

Python einbetten

Anwendungen, die Python einbetten

Beispiele

Unter Linux, FreeBSD und macOS sind Anwendungen normalerweise entweder statisch mit einer libpython verknüpft oder laden dynamisch eine libpython. Die libpython Shared Library ist versioniert, Beispiel: libpython3.12.so für Python 3.12 unter Linux.

Das Vim-Projekt kann die stabile ABI ansprechen. Normalerweise wird die Version des "System-Pythons" verwendet. Es ist derzeit nicht möglich, zu wählen, welche Python-Version verwendet werden soll. Benutzer wünschen sich die Möglichkeit, bei Bedarf eine neuere Python-Version auszuwählen.

Unter Linux ist ein weiterer Ansatz zur Bereitstellung einer Python-einbettenden Anwendung wie GIMP, Python in einem Flatpack-, AppImage- oder Snap-"Container" zu integrieren. In diesem Fall bringt die Anwendung ihre eigene Kopie der Python-Version mit dem Container.

Bibliotheken, die Python einbetten

Beispiele

Dienstprogramme zur Erstellung eigenständiger Anwendungen

Diese Dienstprogramme erstellen eigenständige Anwendungen, sie sind nicht mit libpython verknüpft.

Die Laufzeitkonfiguration festlegen

Marc-André Lemburg forderte eine C-API an, um den Wert einiger Konfigurationsoptionen zur Laufzeit zu setzen.

  • optimization_level
  • verbose
  • parser_debug
  • inspect
  • write_bytecode

Zuvor war es möglich, globale Konfigurationsvariablen direkt zu setzen

  • Py_OptimizeFlag
  • Py_VerboseFlag
  • Py_DebugFlag
  • Py_InspectFlag
  • Py_DontWriteBytecodeFlag

Diese Konfigurationsflags wurden jedoch in Python 3.12 als veraltet markiert und sind für die Entfernung in Python 3.14 vorgesehen.

Spezifikation

Hinzufügen von C-API-Funktionen und -Strukturen zur Konfiguration der Python-Initialisierung

  • Config erstellen
    • Undurchsichtige Struktur PyInitConfig.
    • PyInitConfig_Create().
    • PyInitConfig_Free(config).
  • Optionen abrufen
    • PyInitConfig_HasOption(config, name).
    • PyInitConfig_GetInt(config, name, &value).
    • PyInitConfig_GetStr(config, name, &value).
    • PyInitConfig_GetStrList(config, name, &length, &items).
    • PyInitConfig_FreeStrList().
  • Optionen festlegen
    • PyInitConfig_SetInt(config, name, value).
    • PyInitConfig_SetStr(config, name, value).
    • PyInitConfig_SetStrList(config, name, length, items).
    • PyInitConfig_AddModule(config, name, initfunc)
  • Initialisieren
    • Py_InitializeFromInitConfig(config).
  • Fehlerbehandlung
    • PyInitConfig_GetError(config, &err_msg).
    • PyInitConfig_GetExitcode(config, &exitcode).

Hinzufügen von C-API-Funktionen zum Abrufen und Festlegen der aktuellen Laufzeitkonfiguration

  • PyConfig_Get(name).
  • PyConfig_GetInt(name, &value).
  • PyConfig_Set(name).
  • PyConfig_Names().

Die C-API verwendet null-terminierte UTF-8-kodierte Strings, um auf einen Konfigurationsoptionsnamen zu verweisen.

Diese C-API-Funktionen sind von der begrenzten C-API ausgeschlossen.

PyInitConfig Struktur

Die Struktur PyInitConfig wird durch die Kombination der drei Strukturen der PyConfig API implementiert und enthält auch ein inittab-Mitglied

  • PyPreConfig preconfig
  • PyConfig config
  • PyStatus status
  • struct _inittab *inittab für PyInitConfig_AddModule()

Der PyStatus Status ist nicht mehr getrennt, sondern Teil der vereinheitlichten PyInitConfig Struktur, was die API einfacher zu bedienen macht.

Konfigurationsoptionen

Konfigurationsoptionen werden nach den Membern der PyPreConfig und PyConfig Strukturen benannt. Siehe die PyPreConfig Dokumentation und die PyConfig Dokumentation.

Das Veralten und Entfernen von Konfigurationsoptionen liegt außerhalb des Umfangs der PEP und sollte von Fall zu Fall diskutiert werden.

Öffentliche Konfigurationsoptionen

Folgende Optionen können mit PyConfig_Get() abgerufen und mit PyConfig_Set() gesetzt werden.

Option Typ Kommentar
argv list[str] API: sys.argv.
base_exec_prefix str API: sys.base_exec_prefix.
base_executable str API: sys._base_executable.
base_prefix str API: sys.base_prefix.
bytes_warning int API: sys.flags.bytes_warning.
exec_prefix str API: sys.exec_prefix.
executable str API: sys.executable.
inspect bool API: sys.flags.inspect (int).
int_max_str_digits int API: sys.flags.int_max_str_digits, sys.get_int_max_str_digits() und sys.set_int_max_str_digits().
interactive bool API: sys.flags.interactive.
module_search_paths list[str] API: sys.path.
optimization_level int API: sys.flags.optimize.
parser_debug bool API: sys.flags.debug (int).
platlibdir str API: sys.platlibdir.
prefix str API: sys.base_prefix.
pycache_prefix str API: sys.pycache_prefix.
quiet bool API: sys.flags.quiet (int).
stdlib_dir str API: sys._stdlib_dir.
use_environment bool API: sys.flags.ignore_environment (int).
verbose int API: sys.flags.verbose.
warnoptions list[str] API: sys.warnoptions.
write_bytecode bool API: sys.flags.dont_write_bytecode (int) und sys.dont_write_bytecode (bool).
xoptions dict[str, str] API: sys._xoptions.

Einige Optionsnamen unterscheiden sich von sys Attributen, wie die Option optimization_level und das Attribut sys.flags.optimize. PyConfig_Set() setzt das entsprechende sys Attribut.

Die xoptions sind eine Liste von Strings in PyInitConfig, wobei jeder String das Format key (value ist implizit True) oder key=value hat. In der aktuellen Laufzeitkonfiguration wird daraus ein Dictionary (key: strvalue: str | True).

Schreibgeschützte Konfigurationsoptionen

Folgende Optionen können mit PyConfig_Get() abgerufen, aber nicht mit PyConfig_Set() gesetzt werden.

Option Typ Kommentar
allocator int
buffered_stdio bool
check_hash_pycs_mode str
code_debug_ranges bool
coerce_c_locale bool
coerce_c_locale_warn bool
configure_c_stdio bool
configure_locale bool
cpu_count int API: os.cpu_count() (int | None).
dev_mode bool API: sys.flags.dev_mode.
dump_refs bool
dump_refs_file str
faulthandler bool API: faulthandler.is_enabled().
filesystem_encoding str API: sys.getfilesystemencoding().
filesystem_errors str API: sys.getfilesystemencodeerrors().
hash_seed int
home str
import_time bool
install_signal_handlers bool
isolated bool API: sys.flags.isolated (int).
legacy_windows_fs_encoding bool Nur Windows.
legacy_windows_stdio bool Nur Windows.
malloc_stats bool
orig_argv list[str] API: sys.orig_argv.
parse_argv bool
pathconfig_warnings bool
perf_profiling bool API: sys.is_stack_trampoline_active().
program_name str
run_command str
run_filename str
run_module str
run_presite str benötigt einen Debug-Build.
safe_path bool
show_ref_count bool
site_import bool API: sys.flags.no_site (int).
skip_source_first_line bool
stdio_encoding str API: sys.stdin.encoding, sys.stdout.encoding und sys.stderr.encoding.
stdio_errors str API: sys.stdin.errors, sys.stdout.errors und sys.stderr.errors.
tracemalloc int API: tracemalloc.is_tracing() (bool).
use_frozen_modules bool
use_hash_seed bool
user_site_directory bool API: sys.flags.no_user_site (int).
utf8_mode bool
warn_default_encoding bool
_pystats bool API: sys._stats_on(), sys._stats_off(). Benötigt einen Py_STATS Build.

Config erstellen

Struktur PyInitConfig
Undurchsichtige Struktur zur Konfiguration der Python-Vorinitialisierung und der Python-Initialisierung.
PyInitConfig* PyInitConfig_Create(void):
Erzeugt eine neue Initialisierungskonfiguration mit Standardwerten der isolierten Konfiguration.

Sie muss mit PyInitConfig_Free() freigegeben werden.

Gibt bei Speicherzuordnungsfehlern NULL zurück.

void PyInitConfig_Free(PyInitConfig *config):
Gibt den Speicher einer Initialisierungskonfiguration frei.

Optionen abrufen

Der Parameter name der Konfigurationsoption muss ein nicht-NULL null-terminierter UTF-8-kodierter String sein.

int PyInitConfig_HasOption(PyInitConfig *config, const char *name):
Prüft, ob die Konfiguration eine Option namens name hat.

Gibt 1 zurück, wenn die Option existiert, andernfalls 0.

int PyInitConfig_GetInt(PyInitConfig *config, const char *name, int64_t *value):
Ruft eine Ganzzahl-Konfigurationsoption ab.
  • Setzt *value und gibt bei Erfolg 0 zurück.
  • Setzt einen Fehler in config und gibt bei einem Fehler -1 zurück.
int PyInitConfig_GetStr(PyInitConfig *config, const char *name, char **value):
Ruft eine String-Konfigurationsoption als null-terminierten UTF-8-kodierten String ab.
  • Setzt *value und gibt bei Erfolg 0 zurück.
  • Setzt einen Fehler in config und gibt bei einem Fehler -1 zurück.

Bei Erfolg muss der String mit free(value) freigegeben werden.

int PyInitConfig_GetStrList(PyInitConfig *config, const char *name, size_t *length, char ***items):
Ruft eine String-Listen-Konfigurationsoption als Array von null-terminierten UTF-8-kodierten Strings ab.
  • Setzt *length und *value und gibt bei Erfolg 0 zurück.
  • Setzt einen Fehler in config und gibt bei einem Fehler -1 zurück.

Bei Erfolg muss die String-Liste mit PyInitConfig_FreeStrList(length, items) freigegeben werden.

void PyInitConfig_FreeStrList(size_t length, char **items):
Gibt den Speicher einer String-Liste frei, die von PyInitConfig_GetStrList() erstellt wurde.

Optionen festlegen

Der Parameter name der Konfigurationsoption muss ein nicht-NULL null-terminierter UTF-8-kodierter String sein.

Einige Konfigurationsoptionen haben Nebenwirkungen auf andere Optionen. Diese Logik wird nur implementiert, wenn Py_InitializeFromInitConfig() aufgerufen wird, nicht durch die unten stehenden "Set"-Funktionen. Zum Beispiel setzt das Setzen von dev_mode auf 1 nicht faulthandler auf 1.

int PyInitConfig_SetInt(PyInitConfig *config, const char *name, int64_t value):
Setzt eine Ganzzahl-Konfigurationsoption.
  • Gibt bei Erfolg 0 zurück.
  • Setzt einen Fehler in config und gibt bei einem Fehler -1 zurück.
int PyInitConfig_SetStr(PyInitConfig *config, const char *name, const char *value):
Setzt eine String-Konfigurationsoption aus einem null-terminierten UTF-8-kodierten String. Der String wird kopiert.
  • Gibt bei Erfolg 0 zurück.
  • Setzt einen Fehler in config und gibt bei einem Fehler -1 zurück.
int PyInitConfig_SetStrList(PyInitConfig *config, const char *name, size_t length, char * const *items):
Setzt eine String-Listen-Konfigurationsoption aus einem Array von null-terminierten UTF-8-kodierten Strings. Die String-Liste wird kopiert.
  • Gibt bei Erfolg 0 zurück.
  • Setzt einen Fehler in config und gibt bei einem Fehler -1 zurück.
int PyInitConfig_AddModule(PyInitConfig *config, const char *name, PyObject* (*initfunc)(void)):
Fügt ein integriertes Erweiterungsmodul zur Tabelle der integrierten Module hinzu.

Das neue Modul kann unter dem Namen name importiert werden und verwendet die Funktion initfunc als Initialisierungsfunktion, die beim ersten Importversuch aufgerufen wird.

  • Gibt bei Erfolg 0 zurück.
  • Setzt einen Fehler in config und gibt bei einem Fehler -1 zurück.

Wenn Python mehrmals initialisiert wird, muss PyInitConfig_AddModule() bei jeder Python-Initialisierung aufgerufen werden.

Ähnlich wie die Funktion PyImport_AppendInittab().

Python initialisieren

int Py_InitializeFromInitConfig(PyInitConfig *config):
Initialisiert Python aus der Initialisierungskonfiguration.
  • Gibt bei Erfolg 0 zurück.
  • Setzt einen Fehler in config und gibt bei einem Fehler -1 zurück.
  • Setzt einen Exit-Code in config und gibt -1 zurück, wenn Python beendet werden soll.

Siehe PyInitConfig_GetExitcode() für den Exitcode-Fall.

Fehlerbehandlung

int PyInitConfig_GetError(PyInitConfig* config, const char **err_msg):
Ruft die Fehlermeldung von config ab.
  • Setzt *err_msg und gibt bei gesetzt sein 1 zurück.
  • Setzt *err_msg auf NULL und gibt andernfalls 0 zurück.

Eine Fehlermeldung ist ein UTF-8-kodierter String.

Wenn config einen Exit-Code hat, wird der Exit-Code als Fehlermeldung formatiert.

Die Fehlermeldung bleibt gültig, bis eine andere PyInitConfig Funktion mit config aufgerufen wird. Der Aufrufer muss die Fehlermeldung nicht freigeben.

int PyInitConfig_GetExitcode(PyInitConfig* config, int *exitcode):
Ruft den Exit-Code von config ab.
  • Setzt *exitcode und gibt 1 zurück, wenn Python beendet werden soll.
  • Gibt 0 zurück, wenn config keinen Exit-Code gesetzt hat.

Nur die Funktion Py_InitializeFromInitConfig() kann einen Exit-Code setzen, wenn die Option parse_argv ungleich Null ist.

Ein Exit-Code kann gesetzt werden, wenn die Kommandozeilenanalyse fehlgeschlagen ist (Exit-Code 2) oder wenn eine Kommandozeilenoption die Anzeige der Kommandozeilenhilfe verlangt (Exit-Code 0).

Die Laufzeitkonfiguration abrufen und festlegen

Der Parameter name der Konfigurationsoption muss ein nicht-NULL null-terminierter UTF-8-kodierter String sein.

PyObject* PyConfig_Get(const char *name):
Ruft den aktuellen Laufzeitwert einer Konfigurationsoption als Python-Objekt ab.
  • Gibt bei Erfolg eine neue Referenz zurück.
  • Setzt eine Ausnahme und gibt bei Fehler NULL zurück.

Der Objekttyp hängt von der Option ab: siehe die Tabellen unter Konfigurationsoptionen.

Andere Optionen werden aus internen PyPreConfig und PyConfig Strukturen abgerufen.

Der Aufrufer muss den GIL halten. Die Funktion kann nicht vor der Python-Initialisierung oder nach der Python-Finalisierung aufgerufen werden.

int PyConfig_GetInt(const char *name, int *value):
Ähnlich wie PyConfig_Get(), ruft aber den Wert als Ganzzahl ab.
  • Setzt *value und gibt bei Erfolg 0 zurück.
  • Setzt eine Ausnahme und gibt bei Fehler -1 zurück.
PyObject* PyConfig_Names(void):
Ruft alle Namen von Konfigurationsoptionen als frozenset ab.

Setzt eine Ausnahme und gibt bei Fehler NULL zurück.

Der Aufrufer muss den GIL halten.

PyObject* PyConfig_Set(const char *name, PyObject *value):
Setzt den aktuellen Laufzeitwert einer Konfigurationsoption.
  • Löst einen ValueError aus, wenn keine Option name vorhanden ist.
  • Löst einen ValueError aus, wenn value ein ungültiger Wert ist.
  • Löst einen ValueError aus, wenn die Option schreibgeschützt ist und nicht gesetzt werden kann.
  • Löst einen TypeError aus, wenn value nicht den korrekten Typ hat.

Schreibgeschützte Konfigurationsoptionen können nicht gesetzt werden.

Der Aufrufer muss den GIL halten. Die Funktion kann nicht vor der Python-Initialisierung oder nach der Python-Finalisierung aufgerufen werden.

Stabilität

Das Verhalten von Optionen, die Standardwerte von Optionen und das Verhalten von Python können sich mit jeder Python-Version ändern: sie sind nicht „stabil“.

Darüber hinaus können Konfigurationsoptionen gemäß dem üblichen PEP 387 Deprecation-Prozess hinzugefügt, veraltet und entfernt werden.

Interaktion mit den PyPreConfig und PyConfig APIs

Die Low-Level-APIs PEP 587 PyPreConfig und PyConfig bleiben verfügbar und voll unterstützt. Wie in der Zusammenfassung erwähnt, bleiben sie der bevorzugte Ansatz für Einbettungsfälle, die darauf abzielen, das Verhalten der vollständigen CPython CLI genau nachzuahmen, anstatt lediglich eine Python-Laufzeit als Teil einer größeren Anwendung bereitzustellen.

Die PyPreConfig APIs können in Kombination mit der Initialisierungs-API in dieser PEP verwendet werden. In solchen Fällen gelten die Lese-/Schreibbeschränkungen für vorkonfigurierte Einstellungen für PyInitConfig_SetInt zusätzlich zu PyConfig_Set, nachdem der Interpreter vorkonfiguriert wurde (insbesondere darf nur use_environment aktualisiert werden; ein Versuch, andere vorkonfigurierte Variablen zu aktualisieren, führt zu einer Fehlermeldung).

Beispiele

Python initialisieren

Beispiel für die Initialisierung von Python, Setzen von Konfigurationsoptionen verschiedener Typen, Rückgabe von -1 bei Fehler

int init_python(void)
{
    PyInitConfig *config = PyInitConfig_Create();
    if (config == NULL) {
        printf("PYTHON INIT ERROR: memory allocation failed\n");
        return -1;
    }

    // Set an integer (dev mode)
    if (PyInitConfig_SetInt(config, "dev_mode", 1) < 0) {
        goto error;
    }

    // Set a list of UTF-8 strings (argv)
    char *argv[] = {"my_program", "-c", "pass"};
    if (PyInitConfig_SetStrList(config, "argv",
                                 Py_ARRAY_LENGTH(argv), argv) < 0) {
        goto error;
    }

    // Set a UTF-8 string (program name)
    if (PyInitConfig_SetStr(config, "program_name", L"my_program") < 0) {
        goto error;
    }

    // Initialize Python with the configuration
    if (Py_InitializeFromInitConfig(config) < 0) {
        goto error;
    }
    PyInitConfig_Free(config);
    return 0;

error:
    // Display the error message
    const char *err_msg;
    (void)PyInitConfig_GetError(config, &err_msg);
    printf("PYTHON INIT ERROR: %s\n", err_msg);
    PyInitConfig_Free(config);

    return -1;
}

bytes_warning Option bei der Initialisierung erhöhen

Beispiel für die Erhöhung der Option bytes_warning einer Initialisierungskonfiguration

int config_bytes_warning(PyInitConfig *config)
{
    int64_t bytes_warning;
    if (PyInitConfig_GetInt(config, "bytes_warning", &bytes_warning)) {
        return -1;
    }
    bytes_warning += 1;
    if (PyInitConfig_SetInt(config, "bytes_warning", bytes_warning)) {
        return -1;
    }
    return 0;
}

Die Laufzeit-Verbose-Option abrufen

Beispiel für das Abrufen des aktuellen Laufzeitwerts der Konfigurationsoption verbose

int get_verbose(void)
{
    int verbose;
    if (PyConfig_GetInt("verbose", &verbose) < 0) {
        // Silently ignore the error
        PyErr_Clear();
        return -1;
    }
    return verbose;
}

Im Fehlerfall ignoriert die Funktion den Fehler stillschweigend und gibt -1 zurück. In der Praxis kann das Abrufen der Option verbose nicht fehlschlagen, es sei denn, eine zukünftige Python-Version entfernt die Option.

Implementierung

Abwärtskompatibilität

Änderungen sind vollständig abwärtskompatibel. Es werden nur neue APIs hinzugefügt.

Bestehende APIs wie die PyConfig C API (PEP 587) bleiben unverändert.

Abgelehnte Ideen

Konfiguration als Text

Es wurde vorgeschlagen, die Konfiguration als Text bereitzustellen, um die API mit der stabilen ABI kompatibel zu machen und benutzerdefinierte Optionen zu ermöglichen.

Beispiel

# integer
bytes_warning = 2

# string
filesystem_encoding = "utf8"   # comment

# list of strings
argv = ['python', '-c', 'code']

Die API würde die Konfiguration als String und nicht als Datei entgegennehmen. Beispiel mit einer hypothetischen Funktion PyInit_SetConfig()

void stable_abi_init_demo(int set_path)
{
    PyInit_SetConfig(
        "isolated = 1\n"
        "argv = ['python', '-c', 'code']\n"
        "filesystem_encoding = 'utf-8'\n"
    );
    if (set_path) {
        PyInit_SetConfig("pythonpath = '/my/path'");
    }
}

Das Beispiel ignoriert die Fehlerbehandlung, um die Lesbarkeit zu verbessern.

Das Problem ist, dass die Generierung eines solchen Konfigurationstexts das Hinzufügen von Anführungszeichen zu Strings und das Escapen von Anführungszeichen in Strings erfordert. Das Formatieren eines Arrays von Strings wird dadurch nicht trivial.

Bereitstellung einer API zum Formatieren eines Strings oder eines Arrays von Strings lohnt sich nicht wirklich, während Python direkt eine API bereitstellen kann, um eine Konfigurationsoption zu setzen, bei der der Wert direkt als String oder Array von Strings übergeben wird. Dies vermeidet die Sonderbedeutung bestimmter Zeichen, wie z. B. Zeilenumbruchzeichen, die escaped werden müssten.

Auf eine Option mit einer Ganzzahl verweisen

Die Verwendung von Strings zur Referenzierung einer Konfigurationsoption erfordert den Vergleich von Strings, was langsamer sein kann als der Vergleich von Ganzzahlen.

Verwenden Sie Ganzzahlen, ähnlich wie bei Typen wie „Slots“, z. B. Py_tp_doc, um auf eine Konfigurationsoption zu verweisen. Der Parameter const char *name wird durch int option ersetzt.

Das Akzeptieren benutzerdefinierter Optionen führt eher zu Konflikten bei der Verwendung von Ganzzahlen, da es schwieriger ist, „Namensräume“ (Bereiche) für Ganzzahloptionen aufrechtzuerhalten. Bei der Verwendung von Strings kann ein einfaches Präfix mit einem Doppelpunkt als Trennzeichen verwendet werden.

Ganzzahlen erfordern auch die Pflege einer Liste von Ganzzahlkonstanten und machen somit die C-API und die Python-API größer.

Python 3.13 hat nur etwa 62 Konfigurationsoptionen, daher ist die Leistung kein wirkliches Hindernis. Wenn später eine bessere Leistung benötigt wird, kann eine Hashtabelle verwendet werden, um eine Option anhand ihres Namens zu erhalten.

Wenn das Abrufen einer Konfigurationsoption in Hot Code verwendet wird, kann der Wert einmal gelesen und zwischengespeichert werden. Übrigens können die meisten Konfigurationsoptionen zur Laufzeit nicht geändert werden.

Mehrphasige Initialisierung (ähnlich PEP 432)

Eric Snow äußerte Bedenken, dass dieser Vorschlag bei Einbettungen die Vorstellung verstärken könnte, dass die Initialisierung ein einziger monolithischer Schritt ist. Er argumentierte, dass die Initialisierung 5 verschiedene Phasen umfasst und schlug sogar vor, dass die API dies explizit widerspiegeln sollte. Eric schlug vor, dass zumindest die Implementierung der Initialisierung die Phasen widerspiegeln sollte, teilweise zur Verbesserung der Code-Gesundheit. Insgesamt ähneln seine Erklärungen PEP 432 und PEP 587.

Ein weiterer wichtiger Punkt von Eric, der für diese PEP relevant war, war, dass idealerweise die an Py_InitializeFromConfig() übergebene Konfiguration vollständig sein sollte, bevor diese Funktion aufgerufen wird, während die Initialisierung derzeit die Konfiguration modifiziert.

Obwohl Eric nicht unbedingt eine Alternative zu PEP 741 vorschlug, ist jeder Vorschlag, eine granulare Initialisierungs-API rund um Phasen hinzuzufügen, im Wesentlichen das Gegenteil von dem, was diese PEP zu erreichen versucht. Eine solche API ist komplizierter, sie erfordert das Hinzufügen neuer öffentlicher Strukturen und neuer öffentlicher Funktionen. Sie macht die Python-Initialisierung komplizierter, anstatt dass diese PEP versucht, bestehende APIs zu vereinheitlichen und zu vereinfachen (das Gegenteil). Das Vorhandensein mehrerer Strukturen für ähnliche Zwecke kann zu doppelten Mitgliedern führen, ein ähnliches Problem wie doppelte Mitglieder zwischen den bestehenden PyPreConfig und PyConfig Strukturen.

Locale-Kodierung und breite Zeichenketten

Das Akzeptieren von Strings, die in die Locale-Codierung kodiert sind, und das Akzeptieren von Wide-Strings (wchar_t*) in der PyInitConfig API wurde aufgeschoben, um die PyInitConfig API einfach zu halten und die Komplexität der Python-Vorinitialisierung zu vermeiden. Diese Funktionen werden auch meist benötigt, wenn das Verhalten der vollständigen CPython CLI emuliert wird, und werden daher besser von der Low-Level-API PEP 587 abgedeckt.

Diskussionen


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

Zuletzt geändert: 2024-09-03 13:37:25 GMT