¿Qué significa __all__ en Python?

Resuelto varikin asked hace 16 años • 10 respuestas

Veo __all__en __init__.pyarchivos. ¿Qué hace?

varikin avatar Sep 05 '08 04:09 varikin
Aceptado

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.pyexporta explícitamente los símbolos bary 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>.

Alec Thomas avatar Sep 15 '2008 15:09 Alec Thomas

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.

Jimmy avatar Sep 04 '2008 21:09 Jimmy

¿ Explicar todo en Python?

Sigo viendo la variable __all__configurada en diferentes __init__.pyarchivos.

¿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__.pyconvierte un directorio en un paquete de Python

De los documentos :

Los __init__.pyarchivos 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__.pypuede 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__.pypueden 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__.pyarchivo. 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 packageespacio 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__.pys 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 Bazen la API de nivel superior, en su nivel superior __init__.pypodrí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 Bazesté 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_publicnombre del us.pymó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 exportdecorador

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
Russia Must Remove Putin avatar Feb 29 '2016 21:02 Russia Must Remove Putin

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__.pylos 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. importEsto 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.

MartinStettner avatar May 16 '2013 19:05 MartinStettner