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

Python Enhancement Proposals

PEP 562 – Modul __getattr__ und __dir__

Autor:
Ivan Levkivskyi <levkivskyi at gmail.com>
Status:
Final
Typ:
Standards Track
Erstellt:
09-Sep-2017
Python-Version:
3.7
Post-History:
09-Sep-2017
Resolution:
Python-Dev Nachricht

Inhaltsverzeichnis

Wichtig

Dieses PEP ist ein historisches Dokument. Die aktuelle, kanonische Dokumentation finden Sie unter Customizing Module Attribute Access.

×

Siehe PEP 1, um Änderungen vorzuschlagen.

Zusammenfassung

Es wird vorgeschlagen, die Funktionen __getattr__ und __dir__, die auf Modulen definiert sind, zu unterstützen, um eine grundlegende Anpassung des Zugriffs auf Modulattribute zu ermöglichen.

Begründung

Es ist manchmal praktisch, den Zugriff auf Modulattribute anzupassen oder anderweitig zu kontrollieren. Ein typisches Beispiel ist die Verwaltung von Deprecation-Warnungen. Typische Workarounds sind die Zuweisung von __class__ eines Modulobjekts zu einer benutzerdefinierten Unterklasse von types.ModuleType oder das Ersetzen des Eintrags in sys.modules durch eine benutzerdefinierte Wrapper-Instanz. Es wäre praktisch, dieses Verfahren zu vereinfachen, indem __getattr__, das direkt in einem Modul definiert ist, erkannt wird und wie eine normale __getattr__ Methode funktioniert, mit dem Unterschied, dass es auf Modulinstanzen definiert wird. Zum Beispiel

# lib.py

from warnings import warn

deprecated_names = ["old_function", ...]

def _deprecated_old_function(arg, other):
    ...

def __getattr__(name):
    if name in deprecated_names:
        warn(f"{name} is deprecated", DeprecationWarning)
        return globals()[f"_deprecated_{name}"]
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

# main.py

from lib import old_function  # Works, but emits the warning

Ein weiterer weit verbreiteter Anwendungsfall für __getattr__ wären lazy Submodule-Imports. Betrachten Sie ein einfaches Beispiel

# lib/__init__.py

import importlib

__all__ = ['submod', ...]

def __getattr__(name):
    if name in __all__:
        return importlib.import_module("." + name, __name__)
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

# lib/submod.py

print("Submodule loaded")
class HeavyClass:
    ...

# main.py

import lib
lib.submod.HeavyClass  # prints "Submodule loaded"

Es gibt einen verwandten Vorschlag, PEP 549, der die Unterstützung von Instanzeigenschaften für eine ähnliche Funktionalität vorschlägt. Der Unterschied besteht darin, dass dieses PEP einen schnelleren und einfacheren Mechanismus vorschlägt, aber eine grundlegendere Anpassung bietet. Eine zusätzliche Motivation für diesen Vorschlag ist, dass PEP 484 bereits die Verwendung von Modul __getattr__ zu diesem Zweck in Python-Stub-Dateien definiert, siehe PEP 484.

Darüber hinaus wird vorgeschlagen, die Funktion __dir__ auf Modulebene zu unterstützen, um das Ergebnis eines dir()-Aufrufs auf einem Modul zu ändern und veraltete und andere dynamisch generierte Attribute anzuzeigen. Zum Beispiel

# lib.py

deprecated_names = ["old_function", ...]
__all__ = ["new_function_one", "new_function_two", ...]

def new_function_one(arg, other):
   ...
def new_function_two(arg, other):
    ...

def __dir__():
    return sorted(__all__ + deprecated_names)

# main.py

import lib

dir(lib)  # prints ["new_function_one", "new_function_two", "old_function", ...]

Spezifikation

Die Funktion __getattr__ auf Modulebene sollte ein Argument akzeptieren, das den Namen eines Attributs ist, und den berechneten Wert zurückgeben oder einen AttributeError auslösen

def __getattr__(name: str) -> Any: ...

Wenn ein Attribut nicht durch die normale Suche (d. h. object.__getattribute__) auf einem Modulobjekt gefunden wird, dann wird __getattr__ im __dict__ des Moduls gesucht, bevor ein AttributeError ausgelöst wird. Wenn es gefunden wird, wird es mit dem Attributnamen aufgerufen und das Ergebnis zurückgegeben. Das Nachschlagen eines Namens als globales Modulvariable umgeht das __getattr__ des Moduls. Dies ist beabsichtigt, da andernfalls der Aufruf von __getattr__ für eingebaute Funktionen die Leistung erheblich beeinträchtigen würde.

Die Funktion __dir__ sollte keine Argumente akzeptieren und eine Liste von Zeichenketten zurückgeben, die die auf dem Modul zugänglichen Namen darstellt

def __dir__() -> List[str]: ...

Wenn vorhanden, überschreibt diese Funktion die Standard-dir()-Suche auf einem Modul.

Die Referenzimplementierung für dieses PEP finden Sie unter [2].

Abwärtskompatibilität und Auswirkungen auf die Leistung

Dieses PEP kann Code brechen, der Modul-globale Namen __getattr__ und __dir__ verwendet. (Aber die Sprachreferenz reserviert ausdrücklich *alle* undokumentierten Dunder-Namen und erlaubt "Brechungen ohne Vorwarnung"; siehe [3].) Die Leistungsauswirkungen dieses PEP sind minimal, da __getattr__ nur für fehlende Attribute aufgerufen wird.

Einige Werkzeuge, die die Entdeckung von Modulattributen durchführen, erwarten möglicherweise kein __getattr__. Dieses Problem ist jedoch nicht neu, da es bereits möglich ist, ein Modul durch eine Modulunterklasse mit überschriebenem __getattr__ und __dir__ zu ersetzen, aber mit diesem PEP können solche Probleme häufiger auftreten.

Diskussion

Beachten Sie, dass die Verwendung von Modul __getattr__ Sorgfalt erfordert, um sicherzustellen, dass die referenzierten Objekte pickelbar bleiben. Beispielsweise sollte das Attribut __name__ einer Funktion mit dem Namen übereinstimmen, unter dem sie über __getattr__ zugänglich ist

def keep_pickleable(func):
    func.__name__ = func.__name__.replace('_deprecated_', '')
    func.__qualname__ = func.__qualname__.replace('_deprecated_', '')
    return func

@keep_pickleable
def _deprecated_old_function(arg, other):
    ...

Man sollte auch darauf achten, Rekursionen zu vermeiden, wie man es bei einem __getattr__ auf Klassenebene tun würde.

Um eine Modul-globale Variable mit Auslösen von __getattr__ zu verwenden (zum Beispiel, wenn man ein lazy geladenes Submodul verwenden möchte), kann man darauf zugreifen als

sys.modules[__name__].some_global

oder als

from . import some_global

Beachten Sie, dass letzteres das Modulattribut setzt, wodurch __getattr__ nur einmal aufgerufen wird.

Referenzen


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

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