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

Python Enhancement Proposals

PEP 267 – Optimierter Zugriff auf Modul-Namespaces

Autor:
Jeremy Hylton <jeremy at alum.mit.edu>
Status:
Verschoben
Typ:
Standards Track
Erstellt:
23. Mai 2001
Python-Version:
2.2
Post-History:


Inhaltsverzeichnis

Zurückgestellt

Obwohl diese PEP eine gute Idee ist, hat sich bisher niemand gefunden, der die Unterschiede zwischen dieser PEP, PEP 266 und PEP 280 ausarbeiten könnte. Daher wird sie verschoben.

Zusammenfassung

Diese PEP schlägt eine neue Implementierung von globalen Modul-Namespaces und dem Builtin-Namespace vor, die die Namensauflösung beschleunigt. Die Implementierung würde ein Array von Objektzeigern für die meisten Operationen in diesen Namespaces verwenden. Der Compiler würde zur Kompilierzeit Indizes für globale Variablen und Modulattribute zuweisen.

Die aktuelle Implementierung stellt diese Namespaces als Dictionaries dar. Ein globaler Name verursacht jedes Mal, wenn er verwendet wird, eine Dictionary-Suche; ein Builtin-Name verursacht zwei Dictionary-Suchen, eine fehlgeschlagene Suche im globalen Namespace und eine zweite Suche im Builtin-Namespace.

Diese Implementierung sollte Python-Code beschleunigen, der Modul-Funktionen und -Variablen verwendet. Sie sollte auch umständliche Programmierstile beseitigen, die sich zur Beschleunigung des Zugriffs auf diese Namen entwickelt haben.

Die Implementierung ist kompliziert, da die globalen und Builtin-Namespaces dynamisch auf Arten modifiziert werden können, die für den Compiler nicht erkennbar sind. (Beispiel: Der Namespace eines Moduls wird nach dem Importieren des Moduls durch ein Skript modifiziert.) Infolgedessen muss die Implementierung mehrere Hilfsdatenstrukturen pflegen, um diese dynamischen Merkmale zu erhalten.

Einleitung

Diese PEP schlägt eine neue Implementierung des Attributzugriffs für Modulobjekte vor, die den Zugriff auf zur Kompilierzeit bekannte Modulvariablen optimiert. Das Modul speichert diese Variablen in einem Array und bietet eine Schnittstelle zum Nachschlagen von Attributen anhand von Array-Offsets. Für Globale, Builtins und Attribute importierter Module generiert der Compiler Code, der die Array-Offsets für schnellen Zugriff verwendet.

[Beschreibung der Kernbestandteile des Designs: dlict, Compiler-Unterstützung, Workarounds für dumme Namens-Tricks, Optimierung von Globals anderer Module]

Die Implementierung bewahrt die bestehenden Semantiken für Modul-Namespaces, einschließlich der Möglichkeit, Modul-Namespaces zur Laufzeit so zu modifizieren, dass sie die Sichtbarkeit von Builtin-Namen beeinflussen.

DLict Design

Die Namespaces werden mit einer Datenstruktur implementiert, die manchmal unter dem Namen dlict bekannt ist. Es handelt sich um ein Dictionary, das nummerierte Slots für einige Dictionary-Einträge hat. Der Typ muss in C implementiert werden, um eine akzeptable Leistung zu erzielen. Die neue Typ-Klassen-Vereinigungsarbeit sollte dies recht einfach machen. Die DLict wird wahrscheinlich eine Unterklasse von Dictionary sein, mit einem alternativen Speicher-Modul für einige Schlüssel.

Eine Python-Implementierung ist hier enthalten, um das grundlegende Design zu veranschaulichen

"""A dictionary-list hybrid"""

import types

class DLict:
    def __init__(self, names):
        assert isinstance(names, types.DictType)
        self.names = {}
        self.list = [None] * size
        self.empty = [1] * size
        self.dict = {}
        self.size = 0

    def __getitem__(self, name):
        i = self.names.get(name)
        if i is None:
            return self.dict[name]
        if self.empty[i] is not None:
            raise KeyError, name
        return self.list[i]

    def __setitem__(self, name, val):
        i = self.names.get(name)
        if i is None:
            self.dict[name] = val
        else:
            self.empty[i] = None
            self.list[i] = val
            self.size += 1

    def __delitem__(self, name):
        i = self.names.get(name)
        if i is None:
            del self.dict[name]
        else:
            if self.empty[i] is not None:
                raise KeyError, name
            self.empty[i] = 1
            self.list[i] = None
            self.size -= 1

    def keys(self):
        if self.dict:
            return self.names.keys() + self.dict.keys()
        else:
            return self.names.keys()

    def values(self):
        if self.dict:
            return self.names.values() + self.dict.values()
        else:
            return self.names.values()

    def items(self):
        if self.dict:
            return self.names.items()
        else:
            return self.names.items() + self.dict.items()

    def __len__(self):
        return self.size + len(self.dict)

    def __cmp__(self, dlict):
        c = cmp(self.names, dlict.names)
        if c != 0:
            return c
        c = cmp(self.size, dlict.size)
        if c != 0:
            return c
        for i in range(len(self.names)):
            c = cmp(self.empty[i], dlict.empty[i])
        if c != 0:
            return c
        if self.empty[i] is None:
            c = cmp(self.list[i], dlict.empty[i])
            if c != 0:
                return c
        return cmp(self.dict, dlict.dict)

    def clear(self):
        self.dict.clear()
        for i in range(len(self.names)):
            if self.empty[i] is None:
                self.empty[i] = 1
                self.list[i] = None

    def update(self):
        pass

    def load(self, index):
        """dlict-special method to support indexed access"""
        if self.empty[index] is None:
            return self.list[index]
        else:
            raise KeyError, index # XXX might want reverse mapping

    def store(self, index, val):
        """dlict-special method to support indexed access"""
        self.empty[index] = None
        self.list[index] = val

    def delete(self, index):
        """dlict-special method to support indexed access"""
        self.empty[index] = 1
        self.list[index] = None

Compiler-Probleme

Der Compiler sammelt derzeit die Namen aller globalen Variablen in einem Modul. Dies sind Namen, die auf Modulebene gebunden sind oder in einer Klassen- oder Funktionsdefinition gebunden sind, die sie als global deklariert.

Der Compiler würde Indizes für jeden globalen Namen zuweisen und die Namen und Indizes der Globals zum Codeobjekt des Moduls hinzufügen. Jedes Codeobjekt wäre dann unwiderruflich an das Modul gebunden, in dem es definiert wurde. (Nicht sicher, ob es hier subtile Probleme gibt.)

Für Attribute importierter Module speichert das Modul einen Indirektionsdatensatz. Intern speichert das Modul einen Zeiger auf das definierende Modul und den Offset des Attributs im Array der globalen Variablen des definierenden Moduls. Der Offset würde beim ersten Nachschlagen des Namens initialisiert.

Laufzeitmodell

Die PythonVM wird um neue Opcodes erweitert, um Globale und Modulattribute über ein Modul-Array abzurufen.

Ein Funktions-Objekt müsste auf das Modul zeigen, das es definiert hat, um Zugriff auf das globale Array auf Modulebene zu ermöglichen.

Für Modulattribute, die in der dlict gespeichert sind (nennen wir sie statische Attribute), müsste die get/delattr-Implementierung den Zugriff auf diese Attribute über die alte namensbasierte Schnittstelle verfolgen. Wenn ein statisches Attribut dynamisch aktualisiert wird, z. B.

mod.__dict__["foo"] = 2

Die Implementierung müsste den Array-Slot anstelle des Backup-Dicts aktualisieren.

Abwärtskompatibilität

Die dlict muss Metainformationen darüber pflegen, ob ein Slot derzeit verwendet wird oder nicht. Sie muss auch einen Zeiger auf den Builtin-Namespace pflegen. Wenn ein Name im globalen Namespace nicht gerade verwendet wird, muss die Suche auf den Builtin-Namespace ausweichen.

Umgekehrt muss jedes Modul möglicherweise eine spezielle Zugriffsmethode für den Builtin-Namespace haben, die prüft, ob ein globales Element, das das Builtin überschattet, dynamisch hinzugefügt wurde. Diese Prüfung würde nur erfolgen, wenn eine dynamische Änderung an der dlict des Moduls vorgenommen wird, d. h. wenn ein Name gebunden wird, der zur Kompilierzeit nicht entdeckt wurde.

Diese Mechanismen hätten geringe bis gar keine Kosten für den häufigen Fall, dass der globale Namespace eines Moduls nicht auf ungewöhnliche Weise zur Laufzeit modifiziert wird. Sie würden Overhead für Module hinzufügen, die ungewöhnliche Dinge mit globalen Namen tun, aber das ist eine unübliche Praxis und wahrscheinlich eine, die entmutigt werden sollte.

Es könnte wünschenswert sein, dynamische Ergänzungen zum globalen Namespace in einer zukünftigen Version von Python zu deaktivieren. Wenn ja, könnte die neue Implementierung Warnungen ausgeben.


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

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