Steuern welche Namen aus einem Package sichtbar sind

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
rogerb
User
Beiträge: 528
Registriert: Dienstag 26. November 2019, 23:24

Sonntag 13. Juni 2021, 16:05

Ausgehend von diesem Thread viewtopic.php?f=9&t=52288&start=15#p388977
hab ich mir mal angeschaut wie das asyncio package https://github.com/python/cpython/tree/main/Lib/asyncio auf github aufgebaut ist.
Was ich dabei gelernt habe, ist hier in einem kleinen Demo-Package dargestellt.
  1. Die __init__.py öffnet grundsätzlich die Möglichkeit durch > from <module> import * < alle Namen zu importieren
  2. _unterstrich_namen werden dabei schon mal herausgefiltert
  3. Mit __all__ hat man die Möglichkeit diese Namen die eigentlich nicht öffentlich sichtbar sein sollten, doch wieder sichtbar zu machen.

Packagestruktur:
prog.py
./some_package
__init__.py
main.py
utils.py

Code: Alles auswählen

""" 
Demo for using __all__ and _underscore_names to control
which names can be exposed by a package
"""

import some_package

try:
    print(some_package.regular_main())
except AttributeError as attribute_error:
    print(attribute_error)

try:
    print(some_package._underscore_main())
except AttributeError as attribute_error:
    print(attribute_error)

try:
    print(some_package.regular_utils())
except AttributeError as attribute_error:
    print(attribute_error)

try:
    print(some_package._underscore_utils())
except AttributeError as attribute_error:
    print(attribute_error)
Ausgabe:
regular_main
module 'some_package' has no attribute '_underscore_main'
regular_utils
_underscore_utils

Die Verwendung von _underscore_main wirft eine Exception während _underscore_utils verwendet werden kann

Code: Alles auswählen

""" __init__.py """
from .main import *
from .utils import *

__all__ = main.__all__ + utils.__all__

Code: Alles auswählen

""" main.py 
__all__ only redundently exposes regular_main, keeping _underscore_main hidden
"""

__all__ = ("regular_main",)


def _underscore_main():
    return "_underscore_main"


def regular_main():
    return "regular_main"

Code: Alles auswählen

""" utils.py 
__all__ explicitly exposes the name _underscore_utils overriding the default behavior
"""

__all__ = ("_underscore_utils", "regular_utils")


def _underscore_utils():
    return "_underscore_utils"


def regular_utils():
    return "regular_utils"
Zuletzt geändert von rogerb am Sonntag 13. Juni 2021, 16:34, insgesamt 1-mal geändert.
Sirius3
User
Beiträge: 14998
Registriert: Sonntag 21. Oktober 2012, 17:20

Sonntag 13. Juni 2021, 16:23

@rogerb: eigentlich ist es umgekehrt. Normalerweise werden alle nicht-Unterstrichbamen bei * importiert, mit __all__ kann man explizit einen Teil der Namen auswählen.
rogerb
User
Beiträge: 528
Registriert: Dienstag 26. November 2019, 23:24

Sonntag 13. Juni 2021, 16:29

Sirius3 hat geschrieben:
Sonntag 13. Juni 2021, 16:23
@rogerb: eigentlich ist es umgekehrt. Normalerweise werden alle nicht-Unterstrichbamen bei * importiert, mit __all__ kann man explizit einen Teil der Namen auswählen.
Ja, hatte ich doch so geschrieben. Kann aber sein, dass ich mich unklar ausgedrückt hatte.
Der Code funktioniert jedenfalls genau so.

[edit] Ich hab jetzt noch zwei Fehler bei den Return-Strings korrigiert. Das machte funktional aber keinen Unterschied.
Benutzeravatar
kbr
User
Beiträge: 1292
Registriert: Mittwoch 15. Oktober 2008, 09:27

Sonntag 13. Juni 2021, 16:38

Ohne __all__ werden nur Bezeichner ohne führenden Unterstrich exportiert. Ist __all__ aber definiert, so kann hier explizit angegeben werden, welche Bezeichner exportiert werden. So ist es von der Formulierung her vielleicht etwas einfacher zu verstehen. Übrigens würde ich auch in __init__.py Dateien von * Importen absehen.
rogerb
User
Beiträge: 528
Registriert: Dienstag 26. November 2019, 23:24

Sonntag 13. Juni 2021, 16:49

kbr hat geschrieben:
Sonntag 13. Juni 2021, 16:38
Übrigens würde ich auch in __init__.py Dateien von * Importen absehen.
Ja, so liest man es ja auch in allen möglichen Blogposts.
Ich hatte mich hier aber explizit an der Verwendung im asyncio package orientiert und versucht deren Vorgehensweise nachzubauen.
Ich kann natürlich nur vermuten, warum die das so gemacht haben. Ich denke die Idee ist erstmal alles zuzulassen und dann über _unterstrich_namen und __all__ eine feinere Kontrolle zuzulassen.

Außerdem macht es einen Unterschied ob man*-import innerhalb eines Package oder von außerhalb verwendet, finde ich.
Benutzeravatar
kbr
User
Beiträge: 1292
Registriert: Mittwoch 15. Oktober 2008, 09:27

Sonntag 13. Juni 2021, 17:24

Das mag eine Stilfrage sein. Ich finde es gut wenn Bezeichner im Quellcode über die konkrete Angabe der Namensraum-Hierarchie importiert werden und so schnell auffindbar sind. Deswegen verwende ich Shortcuts in __init__ Dateien, wenn überhaupt, dann möglichst sparsam.
Antworten