¿Qué significa __all__ en Python?
Veo __all__
en __init__.py
archivos. ¿Qué hace?
Vinculado a, pero no mencionado explícitamente aquí, es exactamente cuándo __all__
se usa. Es una lista de cadenas que definen qué símbolos de un módulo se exportarán cuando from <module> import *
se utilicen en el módulo.
Por ejemplo, el siguiente código foo.py
exporta explícitamente los símbolos bar
y baz
:
__all__ = ['bar', 'baz']
waz = 5
bar = 10
def baz(): return 'baz'
Luego, estos símbolos se pueden importar así:
from foo import *
print(bar)
print(baz)
# The following will trigger an exception, as "waz" is not exported by the module
print(waz)
Si se __all__
comenta lo anterior, este código se ejecutará hasta su finalización, ya que el comportamiento predeterminado import *
es importar todos los símbolos que no comiencen con un guión bajo, desde el espacio de nombres dado.
Referencia: https://docs.python.org/tutorial/modules.html#importing-from-a-package
NOTA: __all__
afecta from <module> import *
sólo al comportamiento. Aún se puede acceder a los miembros que no se mencionan __all__
desde fuera del módulo y se pueden importar con from <module> import <member>
.
Es una lista de objetos públicos de ese módulo, según lo interpreta import *
. Anula el valor predeterminado de ocultar todo lo que comienza con un guión bajo.
¿ Explicar todo en Python?
Sigo viendo la variable
__all__
configurada en diferentes__init__.py
archivos.¿Qué hace esto?
¿Que es lo que __all__
hace?
Declara los nombres semánticamente "públicos" de un módulo. Si hay un nombre en __all__
, se espera que los usuarios lo utilicen y pueden tener la expectativa de que no cambiará.
También tendrá efectos programáticos:
import *
__all__
en un módulo, por ejemplo module.py
:
__all__ = ['foo', 'Bar']
significa que cuando sales del módulo, solo se importan import *
los nombres del :__all__
from module import * # imports foo and Bar
Herramientas de documentación
Las herramientas de autocompletado de código y documentación también pueden (de hecho, deberían) inspeccionar el módulo __all__
para determinar qué nombres mostrar como disponibles en un módulo.
__init__.py
convierte un directorio en un paquete de Python
De los documentos :
Los
__init__.py
archivos son necesarios para que Python trate los directorios como si contuvieran paquetes; esto se hace para evitar que los directorios con un nombre común, como una cadena, oculten involuntariamente módulos válidos que aparecen más adelante en la ruta de búsqueda del módulo.
En el caso más simple,
__init__.py
puede ser simplemente un archivo vacío, pero también puede ejecutar el código de inicialización para el paquete o establecer la__all__
variable.
Entonces __init__.py
pueden declarar el __all__
de un paquete .
Administrar una API:
Un paquete normalmente se compone de módulos que pueden importarse entre sí, pero que necesariamente están unidos con un __init__.py
archivo. Ese archivo es lo que hace que el directorio sea un paquete Python real. Por ejemplo, digamos que tiene los siguientes archivos en un paquete:
package
├── __init__.py
├── module_1.py
└── module_2.py
Creemos estos archivos con Python para que puedas seguirlos; puedes pegar lo siguiente en un shell de Python 3:
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
""")
Y ahora ha presentado una API completa que otra persona puede usar cuando importa su paquete, así:
import package
package.foo()
package.Bar()
Y el paquete no tendrá todos los demás detalles de implementación que utilizó al crear sus módulos saturando el package
espacio de nombres.
__all__
en__init__.py
Después de más trabajo, tal vez haya decidido que los módulos son demasiado grandes (¿como muchos miles de líneas?) y necesitan dividirse. Entonces haces lo siguiente:
package
├── __init__.py
├── module_1
│ ├── foo_implementation.py
│ └── __init__.py
└── module_2
├── Bar_implementation.py
└── __init__.py
Primero cree los directorios de subpaquetes con los mismos nombres que los módulos:
subpackage_1 = package / 'module_1'
subpackage_1.mkdir()
subpackage_2 = package / 'module_2'
subpackage_2.mkdir()
Mover las implementaciones:
package_module_1.rename(subpackage_1 / 'foo_implementation.py')
package_module_2.rename(subpackage_2 / 'Bar_implementation.py')
cree __init__.py
s para los subpaquetes que declaran __all__
para cada uno:
(subpackage_1 / '__init__.py').write_text("""
from .foo_implementation import *
__all__ = ['foo']
""")
(subpackage_2 / '__init__.py').write_text("""
from .Bar_implementation import *
__all__ = ['Bar']
""")
Y ahora todavía tienes la API aprovisionada a nivel de paquete:
>>> import package
>>> package.foo()
>>> package.Bar()
<package.module_2.Bar_implementation.Bar object at 0x7f0c2349d210>
Y puede agregar fácilmente cosas a su API que puede administrar en el nivel de subpaquete en lugar del nivel de módulo del subpaquete. Si desea agregar un nuevo nombre a la API, simplemente actualice __init__.py
, por ejemplo, en módulo_2:
from .Bar_implementation import *
from .Baz_implementation import *
__all__ = ['Bar', 'Baz']
Y si no está listo para publicar Baz
en la API de nivel superior, en su nivel superior __init__.py
podría tener:
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
y si sus usuarios conocen la disponibilidad de Baz
, pueden usarlo:
import package
package.Baz()
pero si no lo saben, otras herramientas (como pydoc ) no les informarán.
Luego podrás cambiar eso cuando Baz
esté listo para el horario de máxima audiencia:
from .module_1 import *
from .module_2 import *
__all__ = ['foo', 'Bar', 'Baz']
Prefijo _
versus __all__
:
De forma predeterminada, Python exportará todos los nombres que no comiencen con _
cuando se importen con import *
. Como lo demuestra la sesión de shell aquí, import *
no incluye el _us_non_public
nombre del us.py
módulo:
$ 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']
Ciertamente podrías confiar en este mecanismo. De hecho, algunos paquetes en la biblioteca estándar de Python se basan en esto, pero para hacerlo, asignan alias a sus importaciones, por ejemplo, en ctypes/__init__.py
:
import os as _os, sys as _sys
Usar la _
convención puede ser más elegante porque elimina la redundancia de nombrar los nombres nuevamente. Pero agrega la redundancia para las importaciones (si tiene muchas) y es fácil olvidarse de hacer esto de manera consistente, y lo último que desea es tener que soportar indefinidamente algo que pretendía que fuera solo un detalle de implementación, simplemente porque olvidaste anteponer un _
al nombrar una función.
Personalmente, escribo una __all__
etapa temprana de mi ciclo de vida de desarrollo para módulos para que otros que puedan usar mi código sepan qué deben usar y qué no.
La mayoría de los paquetes de la biblioteca estándar también utilizan __all__
.
Cuando evitar __all__
tiene sentido
Tiene sentido ceñirse a la _
convención de prefijos en lugar de __all__
cuándo:
- Todavía estás en el modo de desarrollo inicial, no tienes usuarios y estás modificando constantemente tu API.
- Tal vez tenga usuarios, pero tiene pruebas unitarias que cubren la API y todavía está agregando activamente la API y modificando el desarrollo.
un export
decorador
La desventaja de usar __all__
es que hay que escribir dos veces los nombres de las funciones y clases que se exportan, y la información se mantiene separada de las definiciones. Podríamos usar un decorador para resolver este problema.
Se me ocurrió la idea de un decorador de exportación de la charla de David Beazley sobre embalaje. Esta implementación parece funcionar bien en el importador tradicional de CPython. Si tiene un gancho o sistema de importación especial, no lo garantizo, pero si lo adopta, es bastante trivial retirarse: solo necesitará volver a agregar manualmente los nombres al archivo__all__
Entonces, por ejemplo, en una biblioteca de utilidades, definirías el decorador:
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
y luego, donde definirías an __all__
, haces esto:
$ 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()
Y esto funciona bien ya sea que se ejecute como principal o se importe mediante otra función.
$ cat > run.py
import main
main.main()
$ python run.py
main
Y el aprovisionamiento de API import *
también funcionará:
$ 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
Sólo agrego esto para ser preciso:
Todas las demás respuestas se refieren a módulos . La pregunta original se menciona explícitamente __all__
en __init__.py
los archivos, por lo que se trata de paquetes de Python .
Generalmente, __all__
sólo entra en juego cuando se utiliza la from xxx import *
variante del enunciado. import
Esto se aplica tanto a paquetes como a módulos.
El comportamiento de los módulos se explica en las otras respuestas. El comportamiento exacto de los paquetes se describe aquí en detalle.
En resumen, __all__
a nivel de paquete hace aproximadamente lo mismo que para los módulos, excepto que trata con módulos dentro del paquete (en contraste con especificar nombres dentro del módulo ). Por lo tanto, __all__
especifica todos los módulos que se cargarán e importarán al espacio de nombres actual cuando usemos from package import *
.
La gran diferencia es que cuando omite la declaración de __all__
en un paquete __init__.py
, la declaración from package import *
no importará nada en absoluto (con las excepciones explicadas en la documentación, consulte el enlace anterior).
Por otro lado, si omite __all__
en un módulo, la "importación destacada" importará todos los nombres (que no comiencen con un guión bajo) definidos en el módulo.