Kan iemand __all__ uitleggen in Python?

Ik gebruik Python steeds vaker en ik blijf de variabele __all__zien in verschillende __init__.py-bestanden. Kan iemand uitleggen wat dit doet?


Antwoord 1, autoriteit 100%

Het is een lijst met openbare objecten van die module, zoals geïnterpreteerd door import *. Het overschrijft de standaard om alles te verbergen dat begint met een onderstrepingsteken.


Antwoord 2, autoriteit 98%

Gelinkt aan, maar hier niet expliciet vermeld, is precies wanneer __all__wordt gebruikt. Het is een lijst met strings die bepalen welke symbolen in een module worden geëxporteerd wanneer from <module> import *wordt gebruikt op de module.

Bijvoorbeeld, de volgende code in een foo.pyexporteert expliciet de symbolen baren Baz:

__all__ = ['bar', 'baz']
waz = 5
bar = 10
def baz(): return 'baz'

Deze symbolen kunnen dan als volgt worden geïmporteerd:

from foo import *
print(bar)
print(baz)
# The following will trigger an exception, as "waz" is not exported by the module
print(waz)

Als de __all__hierboven wordt weggelaten, wordt deze code vervolgens volledig uitgevoerd, aangezien het standaardgedrag van import *is om alle symbolen te importeren die niet beginnen met een onderstrepingsteken, uit de opgegeven naamruimte.

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

OPMERKING:__all__beïnvloedt de from <module> import *alleen gedrag. Leden die niet worden genoemd in __all__zijn nog steeds toegankelijk van buiten de module en kunnen worden geïmporteerd met from <module> import <member>.


Antwoord 3, autoriteit 49%

Leg allesuit in Python?

Ik blijf de variabele __all__zien die is ingesteld in verschillende __init__.py-bestanden.

Wat doet dit?

Wat doet __all__?

Het declareert de semantisch “openbare” namen van een module. Als er een naam in __all__staat, wordt van gebruikers verwacht dat ze deze gebruiken, en kunnen ze de verwachting hebben dat deze niet zal veranderen.

Het zal ook programmatische effecten hebben:

import *

__all__in een module, b.v. module.py:

__all__ = ['foo', 'Bar']

betekent dat wanneer u import *uit de module, alleen die namen in de __all__worden geïmporteerd:

from module import *               # imports foo and Bar

Documentatietools

Hulpprogramma’s voor het automatisch aanvullen van documenten en code kunnen (in feite zouden) ook de __all__moeten inspecteren om te bepalen welke namen moeten worden weergegeven als beschikbaar in een module.

__init__.pymaakt van een directory een Python-pakket

Van de docs:

De __init__.pybestanden zijn nodig om Python de mappen te laten behandelen als pakketten; dit wordt gedaan om te voorkomen dat mappen met een algemene naam, zoals string, onbedoeld geldige modules verbergen die later in het zoekpad van de module voorkomen.

In het eenvoudigste geval kan __init__.pygewoon een leeg bestand zijn, maar het kan ook initialisatiecode voor het pakket uitvoeren of de variabele __all__instellen.

p>

Dus de __init__.pykan de __all__voor een pakketdeclareren.

Een API beheren:

Een pakket bestaat meestal uit modules die elkaar kunnen importeren, maar die noodzakelijkerwijs aan elkaar zijn gekoppeld met een __init__.py-bestand. Dat bestand maakt de directory tot een echt Python-pakket. Stel bijvoorbeeld dat u de volgende bestanden in een pakket hebt:

package
+-- __init__.py
+-- module_1.py
L-- module_2.py

Laten we deze bestanden met Python maken, zodat u het kunt volgen – u kunt het volgende in een Python 3-shell plakken:

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
""")

En nu heb je een complete api gepresenteerd die iemand anders kan gebruiken wanneer ze je pakket importeren, zoals:

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

En het pakket zal niet alle andere implementatiedetails bevatten die u gebruikte bij het maken van uw modules, waardoor de naamruimte van het packagevol zit.

__all__in __init__.py

Misschien heb je na meer werk besloten dat de modules te groot zijn (zoals vele duizenden regels?) en moeten worden opgesplitst. Dus je doet het volgende:

package
+-- __init__.py
+-- module_1
¦   +-- foo_implementation.py
¦   L-- __init__.py
L-- module_2
    +-- Bar_implementation.py
    L-- __init__.py

Maak eerst de subpakketdirectories met dezelfde namen als de modules:

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

Verplaats de implementaties:

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

maak __init__.pys voor de subpakketten die de __all__voor elk declareren:

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

En nu heb je de API nog steeds ingericht op pakketniveau:

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

En u kunt eenvoudig dingen aan uw API toevoegen die u op subpakketniveau kunt beheren in plaats van op subpakketniveau. Als u een nieuwe naam aan de API wilt toevoegen, werkt u eenvoudig de __init__.pybij, b.v. in module_2:

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

En als u nog niet klaar bent om Bazte publiceren in de API op het hoogste niveau, zou u in uw __init__.pyop het hoogste niveau het volgende kunnen hebben:

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

en als uw gebruikers op de hoogte zijn van de beschikbaarheid van Baz, kunnen ze het gebruiken:

import package
package.Baz()

maar als ze er niets van weten, kunnen andere tools (zoals pydoc) zal ze niet informeren.

Je kunt dat later veranderen als Bazklaar is voor prime time:

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

Voorvoegsel _versus __all__:

Standaard exporteert Python alle namen die niet beginnen met een _. U zou zekerop dit mechanisme kunnen vertrouwen. Sommige pakketten in de standaardbibliotheek van Python vertrouwen hier welop, maar om dit te doen, aliasen ze hun import, bijvoorbeeld in ctypes/__init__.py:

import os as _os, sys as _sys

Het gebruik van de _conventie kan eleganter zijn omdat het de overbodigheid van het opnieuw benoemen van namen verwijdert. Maar het voegt de redundantie toe voor import (als je er veel hebt) en het is gemakkelijkom te vergeten dit consequent te doen – en het laatste wat je wilt is voor onbepaalde tijd iets moeten ondersteunen dat je van plan was alleen een implementatiedetail zijn, alleen omdat je bent vergeten een _voor te voegen bij het benoemen van een functie.

Ik schrijf persoonlijk een __all__vroeg in mijn ontwikkelingslevenscyclus voor modules, zodat anderen die mijn code kunnen gebruiken, weten wat ze wel en niet moeten gebruiken.

De meeste pakketten in de standaardbibliotheek gebruiken ook __all__.

Als het vermijden van __all__zinvol is

Het is logisch om vast te houden aan de prefix-conventie _in plaats van __all__wanneer:

  • Je bevindt je nog in de vroege ontwikkelingsmodus en hebt geen gebruikers, en je bent constant bezig met het aanpassen van je API.
  • Misschien heb je wel gebruikers, maar je hebt unittests die betrekking hebben op de API, en je bent nog steeds actief bezig met het toevoegen van de API en tweaken in ontwikkeling.

Een exportdecorateur

Het nadeel van het gebruik van __all__is dat je de namen van functies en klassen die geëxporteerd worden twee keer moet schrijven – en de informatie wordt gescheiden gehouden van de definities. We kunneneen binnenhuisarchitect gebruiken om dit probleem op te lossen.

Ik kreeg het idee voor zo’n exportdecorateur uit de talk van David Beazley over verpakkingen. Deze implementatie lijkt goed te werken in de traditionele importeur van CPython. Als je een speciale importhaak of -systeem hebt, kan ik dit niet garanderen, maar als je het gebruikt, is het vrij triviaal om terug te gaan – je hoeft alleen de namen handmatig weer toe te voegen aan de __all__

Dus in bijvoorbeeld een hulpprogrammabibliotheek zou u de decorateur definiëren:

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

en dan, waar je een __all__definieert, doe je dit:

$ 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()

En dit werkt prima, of het nu als hoofdfunctie wordt uitgevoerd of door een andere functie wordt geïmporteerd.

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

En API-inrichting met import *werkt ook:

$ 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

Antwoord 4, autoriteit 25%

Ik voeg dit gewoon toe om precies te zijn:

Alle andere antwoorden verwijzen naar modules. De oorspronkelijke vraag vermeldde expliciet __all__in __init__.pybestanden, dus dit gaat over python pakketten.

Over het algemeen komt __all__alleen in het spel wanneer de variant from xxx import *van de import-instructie wordt gebruikt. Dit geldt zowel voor pakketten als voor modules.

Het gedrag van modules wordt uitgelegd in de andere antwoorden. Het exacte gedrag van pakketten wordt hierin detail beschreven.

Kortom, __all__op pakketniveau doet ongeveer hetzelfde als voor modules, behalve dat het zich bezighoudt met modules binnen het pakket(in tegenstelling tot het specificeren van namen binnen de module). Dus __all__specificeert alle modules die zullen worden geladen en geïmporteerd in de huidige naamruimte wanneer we from package import *gebruiken.

Het grote verschil is dat wanneer u weglaatde declaratie van __all__in de __init__.pyvan een pakket, de instructie from package import *zal helemaal niets importeren (met uitzonderingen uitgelegd in de documentatie, zie link hierboven).

Aan de andere kant, als u __all__in een module weglaat, importeert de “import met ster” alle namen (niet beginnend met een onderstrepingsteken) die in de module zijn gedefinieerd.


Antwoord 5, autoriteit 12%

Het verandert ook wat pydoc zal tonen:

module1.py

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

module2.py

__all__ = ['a', 'b']
a = "A"
b = "B"
c = "C"

$ pydoc module1

Help bij module module1:
NAAM
  module 1
BESTAND
  module1.py
GEGEVENS
  a= 'A'
  b= 'B'
  c= 'C'

$ pydoc module2

Help bij module module2:
NAAM
  module2
BESTAND
  module2.py
GEGEVENS
  __all__= ['a', 'b']
  a= 'A'
  b= 'B'

Ik verklaar __all__in al mijn modules, en onderstreep interne details, deze helpen echt bij het gebruik van dingen die je nog nooit eerder hebt gebruikt in live tolksessies.


Antwoord 6, autoriteit 10%

__all__past *aan in from <module> import *
en from <package> import *.


Een moduleis een .py-bestand dat moet worden geïmporteerd.

Een pakketis een map met een __init__.py-bestand. Een pakket bevat meestal modules.


MODULES

""" cheese.py - an example module """
__all__ = ['swiss', 'cheddar']
swiss = 4.99
cheddar = 3.99
gouda = 10.99

__all__laat mensen de “openbare” functies van een modulekennen.[@AaronHall]Pydoc herkent ze ook.[@Longpoke]

van moduleimport *

Zie hoe swissen cheddarin de lokale naamruimte worden gebracht, maar niet gouda:

>>> from cheese import *
>>> swiss, cheddar
(4.99, 3.99)
>>> gouda
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'gouda' is not defined

Zonder __all__zou elk symbool (dat niet begint met een onderstrepingsteken) beschikbaar zijn geweest.


Import zonder *wordt niet beïnvloed door __all__


importeer module

>>> import cheese
>>> cheese.swiss, cheese.cheddar, cheese.gouda
(4.99, 3.99, 10.99)

van moduleimporteer namen

>>> from cheese import swiss, cheddar, gouda
>>> swiss, cheddar, gouda
(4.99, 3.99, 10.99)

importeer moduleals lokale naam

>>> import cheese as ch
>>> ch.swiss, ch.cheddar, ch.gouda
(4.99, 3.99, 10.99)

PAKKETTEN

In het bestand __init__.pyvan een pakket__all__staat een lijst met strings met de namen van openbare modules of andere objecten. Deze functies zijn beschikbaar voor invoer met jokertekens. Net als bij modules past __all__de *aan bij het importeren met jokertekens uit het pakket.[@MartinStettner]

Hier is een fragment uit de Python MySQL-connector__init__.py:

__all__ = [
    'MySQLConnection', 'Connect', 'custom_error_exception',
    # Some useful constants
    'FieldType', 'FieldFlag', 'ClientFlag', 'CharacterSet', 'RefreshOption',
    'HAVE_CEXT',
    # Error handling
    'Error', 'Warning',
    ...etc...
    ]

Het standaard geval, sterretje zonder __all__voor een pakket, is ingewikkeld, omdat het voor de hand liggende gedrag duur zou zijn: het bestandssysteem gebruiken om naar alle modules in het pakket te zoeken. In plaats daarvan worden bij het lezen van de documenten alleen de objecten geïmporteerd die zijn gedefinieerd in __init__.py:

Als __all__niet is gedefinieerd, importeert de instructie from sound.effects import *nietalle submodules uit het pakket sound.effectsin de huidige naamruimte; het zorgt er alleen voor dat het pakket sound.effectsis geïmporteerd (mogelijk wordt een initialisatiecode uitgevoerd in __init__.py) en importeert vervolgens de namen die in het pakket zijn gedefinieerd. Dit omvat alle namen die zijn gedefinieerd (en submodules die expliciet zijn geladen) door __init__.py. Het bevat ook alle submodules van het pakket die expliciet zijn geladen door eerdere importinstructies.


En tot slot, een vereerde traditie voor stack-overflow-antwoorden, professoren en mansplainers overal, is het bon motvan verwijt voor het stellen van een vraag in de eerste plaats:

Invoer van wildcards … moet worden vermeden, omdat ze lezers en veel geautomatiseerde tools [verwarren].

[PEP 8, @ToolmakerSteve]


Antwoord 7, autoriteit 7%

Van (een onofficiële) Python-referentiewiki:

De openbare namen gedefinieerd door een module worden bepaald door de naamruimte van de module te controleren op een variabele met de naam __all__; indien gedefinieerd, moet het een reeks strings zijn die namen zijn die door die module zijn gedefinieerd of geïmporteerd. De namen in __all__worden allemaal als openbaar beschouwd en moeten bestaan. Als __all__niet is gedefinieerd, bevat de set openbare namen alle namen in de naamruimte van de module die niet beginnen met een onderstrepingsteken (“_”). __all__moet de volledige openbare API bevatten. Het is bedoeld om te voorkomen dat per ongeluk items worden geëxporteerd die geen deel uitmaken van de API (zoals bibliotheekmodules die zijn geïmporteerd en gebruikt binnen de module).


Antwoord 8

Kort antwoord

__all__beïnvloedt from <module> import *verklaringen.

Lang antwoord

Beschouw dit voorbeeld:

foo
+-- bar.py
L-- __init__.py

In foo/__init__.py:

  • (Impliciet) Als we __all__niet definiëren, dan zal from foo import *alleen namen importeren die zijn gedefinieerd in foo/__init__.py.

  • (Expliciet) Als we __all__ = []definiëren, dan zal from foo import *niets importeren.

  • (Expliciet) Als we __all__ = [ <name1>, ... ]definiëren, dan zal from foo import *alleen die namen importeren.

Houd er rekening mee dat python in het impliciete geval geen namen importeert die beginnen met _. U kunt het importeren van dergelijke namen echter forceren met __all__.

Je kunt het Python-document hier.


Antwoord 9

__all__wordt gebruikt om de openbare API van een Python-module te documenteren. Hoewel het optioneel is, moet __all__worden gebruikt.

Hier is het relevante fragment uit de Python-taalreferentie:

De openbare namen gedefinieerd door een module worden bepaald door de naamruimte van de module te controleren op een variabele met de naam __all__; indien gedefinieerd, moet het een reeks strings zijn die namen zijn die door die module zijn gedefinieerd of geïmporteerd. De namen in __all__worden allemaal als openbaar beschouwd en moeten bestaan. Als __all__niet is gedefinieerd, bevat de set openbare namen alle namen in de naamruimte van de module die niet beginnen met een onderstrepingsteken (‘_’). __all__moet de volledige openbare API bevatten. Het is bedoeld om te voorkomen dat per ongeluk items worden geëxporteerd die geen deel uitmaken van de API (zoals bibliotheekmodules die zijn geïmporteerd en gebruikt binnen de module).

PEP 8gebruikt vergelijkbare bewoordingen , hoewel het ook duidelijk maakt dat geïmporteerde namen geen deel uitmaken van de openbare API wanneer __all__afwezig is:

Om introspectie beter te ondersteunen, moeten modules de namen expliciet in hun openbare API declareren met het kenmerk __all__. Als u __all__instelt op een lege lijst, geeft dit aan dat de module geen openbare API heeft.

[…]

Geïmporteerde namen moeten altijd worden beschouwd als een implementatiedetail. Andere modules mogen niet vertrouwen op indirecte toegang tot dergelijke geïmporteerde namen, tenzij ze een expliciet gedocumenteerd onderdeel zijn van de API van de bevattende module, zoals os.pathof de module __init__van een pakket die onthult functionaliteit van submodules.

Bovendien, zoals aangegeven in andere antwoorden, wordt __all__gebruikt om wildcard importeren voor pakketten:

De import-instructie gebruikt de volgende conventie: als de code van een pakket __init__.pyeen lijst definieert met de naam __all__, wordt dit beschouwd als de lijst met modulenamen die moet worden geïmporteerd wanneer from package import *wordt aangetroffen.


Antwoord 10

__all__beïnvloedt hoe from foo import *werkt.

Code die zich in de hoofdtekst van een module bevindt (maar niet in de hoofdtekst van een functie of klasse) mag een asterisk (*) gebruiken in een from-instructie:

from foo import *

De *verzoekt om alle attributen van module foo(behalve die beginnen met underscores) als globale variabelen in de importmodule te binden. Als fooeen attribuut __all__heeft, is de waarde van het attribuut de lijst met namen die gebonden zijn aan dit type from-instructie.

Als fooeen pakketis en de __init__.pyervan een lijst definieert met de naam __all__, wordt deze genomen om de lijst met namen van submodules te zijn die moeten worden geïmporteerd wanneer from foo import *wordt aangetroffen. Als __all__niet is gedefinieerd, importeert de instructie from foo import *de namen die in het pakket zijn gedefinieerd. Dit omvat alle namen die zijn gedefinieerd (en submodules die expliciet zijn geladen) door __init__.py.

Merk op dat __all__geen lijst hoeft te zijn. Volgens de documentatie over de import-instructie , indien gedefinieerd, __all__moet een reeks stringszijn, dit zijn namen die door de module zijn gedefinieerd of geïmporteerd. Je kunt dus net zo goed een tuple gebruiken om wat geheugen- en CPU-cycli te op te slaan. Vergeet alleen een komma niet voor het geval de module een enkele openbare naam definieert:

__all__ = ('some_name',)

Zie ook Waarom is import * slecht?


Antwoord 11

Dit is gedefinieerd in PEP8 hier:

Algemene namen van variabelen

(Laten we hopen dat deze variabelen bedoeld zijn voor gebruik in slechts één module.) De conventies zijn ongeveer hetzelfde als die voor functies.

Modules die zijn ontworpen voor gebruik via from M import *moeten het __all__-mechanisme gebruiken om het exporteren van globals te voorkomen, of de oudere conventie gebruiken om zulke globals vooraf te laten gaan met een onderstrepingsteken (wat je misschien wilt doen om aan te geven dat deze globals “module niet-openbaar” zijn).

PEP8 biedt coderingsconventies voor de Python-code die de standaardbibliotheek in de hoofddistributie van Python omvat. Hoe meer je dit volgt, hoe dichter je bij de oorspronkelijke bedoeling komt.

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Other episodes