1740 Stimmen

Was bedeutet __all__ in Python?

Ich sehe __all__ en __init__.py Dateien. Was wird damit gemacht?

1595voto

Alec Thomas Punkte 17213

Damit verbunden, aber hier nicht ausdrücklich erwähnt, ist die Frage, wann genau __all__ verwendet wird. Es handelt sich um eine Liste von Zeichenketten, die festlegen, welche Symbole in einem Modul exportiert werden, wenn from <module> import * auf dem Modul verwendet wird.

Zum Beispiel kann der folgende Code in einer foo.py exportiert ausdrücklich die Symbole bar y baz :

__all__ = ['bar', 'baz']

waz = 5
bar = 10
def baz(): return 'baz'

Diese Symbole können dann wie folgt importiert werden:

from foo import *

print(bar)
print(baz)

# The following will trigger an exception, as "waz" is not exported by the module
print(waz)

Wenn die __all__ auskommentiert wird, wird dieser Code bis zum Ende ausgeführt, da das Standardverhalten von import * ist es, alle Symbole, die nicht mit einem Unterstrich beginnen, aus dem angegebenen Namensraum zu importieren.

Referenz: https://docs.python.org/tutorial/modules.html#importing-from-a-package

HINWEIS: __all__ beeinflusst die from <module> import * nur Verhalten. Mitglieder, die nicht in __all__ sind weiterhin von außerhalb des Moduls zugänglich und können mit from <module> import <member> .

866voto

Jimmy Punkte 85199

Es ist eine Liste der öffentlichen Objekte dieses Moduls, wie sie von import * . Sie setzt die Standardeinstellung außer Kraft, die alles ausblendet, was mit einem Unterstrich beginnt.

617voto

Erklären Sie todo in Python?

Ich sehe ständig die Variable __all__ in verschiedenen __init__.py Dateien.

Was bewirkt dies?

Was bedeutet __all__ tun?

Es deklariert die semantisch "öffentlichen" Namen eines Moduls. Wenn es einen Namen in __all__ Von den Nutzern wird erwartet, dass sie es verwenden, und sie können davon ausgehen, dass es sich nicht ändern wird.

Sie wird auch programmatische Auswirkungen haben:

import *

__all__ in einem Modul, z.B. module.py :

__all__ = ['foo', 'Bar']

bedeutet, dass, wenn Sie import * aus dem Modul, nur die Namen in der Datei __all__ importiert werden:

from module import *               # imports foo and Bar

Dokumentationstools

Dokumentations- und Code-Autovervollständigungstools können (und sollten sogar) auch die __all__ um zu bestimmen, welche Namen von einem Modul als verfügbar angezeigt werden sollen.

__init__.py macht ein Verzeichnis zu einem Python-Paket

Von der docs :

Le site __init__.py Dateien sind erforderlich, damit Python die Verzeichnisse so behandelt, als enthielten sie Pakete; dies wird gemacht, um zu verhindern, dass Verzeichnisse mit einem gemeinsamen Namen, wie z.B. string, unbeabsichtigt gültige Module verstecken, die später auf dem Modulsuchpfad vorkommen.

Im einfachsten Fall, __init__.py kann einfach eine leere Datei sein, aber sie kann auch Initialisierungscode für das Paket ausführen oder die __all__ variabel.

Also die __init__.py kann die __all__ für eine Paket .

Verwalten einer API:

Ein Paket besteht in der Regel aus Modulen, die sich gegenseitig importieren können, aber notwendigerweise mit einer __init__.py Datei. Diese Datei macht das Verzeichnis zu einem echten Python-Paket. Nehmen wir zum Beispiel an, Sie haben die folgenden Dateien in einem Paket:

package
 __init__.py
 module_1.py
 module_2.py

Lassen Sie uns diese Dateien mit Python erstellen, damit Sie mitverfolgen können - Sie könnten das Folgende in eine Python 3-Shell einfügen:

from pathlib import Path

package = Path('package')
package.mkdir()

(package / '__init__.py').write_text("""
from .module_1 import *
from .module_2 import *
""")

package_module_1 = package / 'module_1.py'
package_module_1.write_text("""
__all__ = ['foo']
imp_detail1 = imp_detail2 = imp_detail3 = None
def foo(): pass
""")

package_module_2 = package / 'module_2.py'
package_module_2.write_text("""
__all__ = ['Bar']
imp_detail1 = imp_detail2 = imp_detail3 = None
class Bar: pass
""")

Und nun haben Sie eine vollständige API präsentiert, die jemand anderes verwenden kann, wenn er Ihr Paket importiert, etwa so:

import package
package.foo()
package.Bar()

Und das Paket wird nicht all die anderen Implementierungsdetails enthalten, die Sie bei der Erstellung Ihrer Module verwendet haben und die in der package Namensraum.

__all__ en __init__.py

Nach weiterer Arbeit kommen Sie vielleicht zu dem Schluss, dass die Module zu groß sind (viele tausend Zeilen?) und aufgeteilt werden müssen. Also tun Sie Folgendes:

package
 __init__.py
 module_1
    foo_implementation.py
    __init__.py
 module_2
     Bar_implementation.py
     __init__.py

Erstellen Sie zunächst die Verzeichnisse der Unterpakete mit denselben Namen wie die Module:

subpackage_1 = package / 'module_1'
subpackage_1.mkdir()
subpackage_2 = package / 'module_2'
subpackage_2.mkdir()

Verschieben Sie die Implementierungen:

package_module_1.rename(subpackage_1 / 'foo_implementation.py')
package_module_2.rename(subpackage_2 / 'Bar_implementation.py')

erstellen. __init__.py s für die Unterpakete, die die __all__ für jeden:

(subpackage_1 / '__init__.py').write_text("""
from .foo_implementation import *
__all__ = ['foo']
""")
(subpackage_2 / '__init__.py').write_text("""
from .Bar_implementation import *
__all__ = ['Bar']
""")

Und jetzt haben Sie die API immer noch auf der Paketebene bereitgestellt:

>>> import package
>>> package.foo()
>>> package.Bar()
<package.module_2.Bar_implementation.Bar object at 0x7f0c2349d210>

Und Sie können ganz einfach Dinge zu Ihrer API hinzufügen, die Sie auf der Ebene des Unterpakets verwalten können, anstatt auf der Modulebene des Unterpakets. Wenn Sie der API einen neuen Namen hinzufügen wollen, aktualisieren Sie einfach die __init__.py , z.B. in Modul_2:

from .Bar_implementation import *
from .Baz_implementation import *
__all__ = ['Bar', 'Baz']

Und wenn Sie noch nicht bereit sind zu veröffentlichen Baz in der obersten API-Ebene, in Ihrer obersten Ebene __init__.py können Sie haben:

from .module_1 import *       # also constrained by __all__'s
from .module_2 import *       # in the __init__.py's
__all__ = ['foo', 'Bar']     # further constraining the names advertised

und ob Ihre Nutzer über die Verfügbarkeit von Baz können sie es verwenden:

import package
package.Baz()

aber wenn sie es nicht wissen, können andere Instrumente (wie pydoc ) wird sie nicht informieren.

Sie können dies später ändern, wenn Baz ist reif für die Primetime:

from .module_1 import *
from .module_2 import *
__all__ = ['foo', 'Bar', 'Baz']

Vorwahlen _ gegen __all__ :

Standardmäßig exportiert Python alle Namen, die nicht mit einem _ beim Import mit import * . Wie die Shell-Sitzung hier zeigt, import * bringt nicht die _us_non_public Name aus dem us.py Modul:

$ cat us.py
USALLCAPS = "all caps"
us_snake_case = "snake_case"
_us_non_public = "shouldn't import"
$ python
Python 3.10.0 (default, Oct  4 2021, 17:55:55) [GCC 10.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from us import *
>>> dir()
['USALLCAPS', '__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'us_snake_case']

Sie haben sicherlich könnte sich auf diesen Mechanismus verlassen. Einige Pakete in der Python-Standardbibliothek, in der Tat, tun verlassen sich darauf, aber um dies zu tun, geben sie ihren Importen einen Alias, zum Beispiel in ctypes/__init__.py :

import os as _os, sys as _sys

Die Verwendung des _ Konvention kann eleganter sein, weil sie die Redundanz der erneuten Nennung der Namen beseitigt. Aber es fügt die Redundanz für Importe hinzu (wenn Sie eine Menge davon haben) und es ist einfach Und das Letzte, was Sie wollen, ist, etwas auf unbestimmte Zeit unterstützen zu müssen, was nur ein Implementierungsdetail sein sollte, nur weil Sie vergessen haben, ein _ wenn Sie eine Funktion benennen.

Ich persönlich schreibe eine __all__ zu einem frühen Zeitpunkt in meinem Entwicklungszyklus für Module, damit andere, die meinen Code verwenden könnten, wissen, was sie verwenden und nicht verwenden sollten.

Die meisten Pakete der Standardbibliothek verwenden auch __all__ .

Bei der Vermeidung von __all__ macht Sinn

Es ist sinnvoll, sich an die _ pefix-Konvention anstelle von __all__ wenn:

  • Sie befinden sich noch in einem frühen Entwicklungsstadium, haben noch keine Nutzer und arbeiten ständig an der Optimierung Ihrer API.
  • Vielleicht haben Sie Benutzer, aber Sie haben Unittests, die die API abdecken, und Sie sind immer noch aktiv dabei, die API zu erweitern und in der Entwicklung zu optimieren.

Eine export Tapezierer

Der Nachteil der Verwendung von __all__ ist, dass Sie die Namen von Funktionen und Klassen, die exportiert werden, zweimal schreiben müssen - und die Informationen werden von den Definitionen getrennt gehalten. Wir könnte einen Dekorator verwenden, um dieses Problem zu lösen.

Die Idee für einen solchen Export-Dekorateur habe ich von David Beazleys Vortrag über Verpackungen. Diese Implementierung scheint im traditionellen Importer von CPython gut zu funktionieren. Wenn Sie einen speziellen Import-Hook oder ein spezielles System haben, garantiere ich nicht dafür, aber wenn Sie es übernehmen, ist es ziemlich trivial, es wieder rückgängig zu machen - Sie müssen nur die Namen manuell wieder in die __all__

In einer Utility-Bibliothek würden Sie also beispielsweise den Dekorator definieren:

import sys

def export(fn):
    mod = sys.modules[fn.__module__]
    if hasattr(mod, '__all__'):
        mod.__all__.append(fn.__name__)
    else:
        mod.__all__ = [fn.__name__]
    return fn

und dann, wo Sie eine __all__ tun Sie dies:

$ cat > main.py
from lib import export
__all__ = [] # optional - we create a list if __all__ is not there.

@export
def foo(): pass

@export
def bar():
    'bar'

def main():
    print('main')

if __name__ == '__main__':
    main()

Und das funktioniert problemlos, egal ob als Hauptfunktion ausgeführt oder von einer anderen Funktion importiert.

$ cat > run.py
import main
main.main()

$ python run.py
main

Und API-Bereitstellung mit import * wird auch funktionieren:

$ cat > run.py
from main import *
foo()
bar()
main() # expected to error here, not exported

$ python run.py
Traceback (most recent call last):
  File "run.py", line 4, in <module>
    main() # expected to error here, not exported
NameError: name 'main' is not defined

200voto

MartinStettner Punkte 27921

Ich füge dies nur der Genauigkeit halber hinzu:

Alle anderen Antworten beziehen sich auf Module . In der ursprünglichen Frage wurde ausdrücklich erwähnt __all__ en __init__.py Dateien, hier geht es also um python Pakete .

Im Allgemeinen, __all__ kommt nur ins Spiel, wenn die from xxx import * Variante des import Anweisung verwendet wird. Dies gilt sowohl für Pakete als auch für Module.

Das Verhalten bei Modulen wird in den anderen Antworten erläutert. Das genaue Verhalten für Pakete wird beschrieben aquí im Detail.

Kurz gesagt, __all__ auf Paketebene tut ungefähr dasselbe wie für Module, außer dass es sich mit Module innerhalb des Pakets (im Gegensatz zur Angabe von Namen innerhalb des Moduls ). Also __all__ gibt alle Module an, die in den aktuellen Namespace geladen und importiert werden sollen, wenn wir from package import * .

Der große Unterschied besteht darin, dass Sie, wenn Sie auslassen. die Erklärung von __all__ in einem Paket von __init__.py die Aussage from package import * wird überhaupt nichts importiert (mit Ausnahmen, die in der Dokumentation erklärt werden, siehe Link oben).

Andererseits, wenn Sie Folgendes weglassen __all__ in einem Modul, importiert der "starred import" alle im Modul definierten Namen (die nicht mit einem Unterstrich beginnen).

98voto

Es ändert auch, was pydoc anzeigt:

modul1.py

a = "A"
b = "B"
c = "C"

modul2.py

__all__ = ['a', 'b']

a = "A"
b = "B"
c = "C"

$ pydoc Modul1

Help on module module1:

**NAME**
    module1

**FILE**
    module1.py

**DATA**
    **a** = 'A'
    **b** = 'B'
    **c** = 'C'

$ pydoc Modul2

Help on module module2:

**NAME**
    module2

**FILE**
    module2.py

**DATA**
    **\_\_all\_\_** = \['a', 'b'\]
    **a** = 'A'
    **b** = 'B'

Ich erkläre __all__ in allen meinen Modulen sowie die Unterstreichung interner Details, die wirklich hilfreich sind, wenn man Dinge, die man noch nie benutzt hat, in Live-Dolmetschersitzungen verwendet.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X