Este grupo de especialistas pasados muchos días de trabajo y de recopilar de datos, dieron con la respuesta, nuestro deseo es que te sea de utilidad para tu plan.
Solución:
Vinculado a, pero no mencionado explícitamente aquí, es exactamente cuando __all__
se utiliza. Es una lista de cadenas que definen qué símbolos de un módulo se exportarán cuando from
se utiliza en el módulo.
Por ejemplo, el siguiente código en un foo.py
exporta explícitamente los símbolos bar
y baz
:
__all__ = ['bar', 'baz']
waz = 5
bar = 10
def baz(): return 'baz'
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 el __all__
anterior, este código se ejecutará hasta su finalización, como el comportamiento predeterminado de 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 el from
solo comportamiento. Miembros que no se mencionan en __all__
todavía son accesibles desde fuera del módulo y se pueden importar con from
.
Es una lista de objetos públicos de ese módulo, interpretada por import *
. Anula el valor predeterminado de ocultar todo lo que comienza con un guión bajo.
¿Explicar __all__ en Python?
Sigo viendo la variable
__all__
ambientado en diferentes__init__.py
archivos.¿Qué hace esto?
Que hace __all__
¿hacer?
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, p. ej. module.py
:
__all__ = ['foo', 'Bar']
significa que cuando tu import *
del módulo, solo aquellos nombres en el __all__
son importados:
from module import * # imports foo and Bar
Herramientas de documentación
Las herramientas de autocompletado de código y documentación pueden (de hecho, deberían) inspeccionar __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
los archivos son necesarios para que Python trate los directorios como si fueran paquetes; esto se hace para evitar directorios con un nombre común, como string, para que no oculte involuntariamente módulos válidos que se produzcan más adelante en la ruta de búsqueda del módulo.En el caso más simple,
__init__.py
puede ser un archivo vacío, pero también puede ejecutar el código de inicialización para el paquete o establecer el__all__
variable.
Entonces el __init__.py
puede declarar el __all__
para paquete.
Administrar una API:
Un paquete generalmente se compone de módulos que pueden importarse entre sí, pero que están necesariamente vinculados con un __init__.py
expediente. Ese archivo es lo que hace que el directorio sea un paquete de Python real. Por ejemplo, supongamos 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 importe su paquete, así:
import package
package.foo()
package.Bar()
Y el paquete no tendrá todos los demás detalles de implementación que usó al crear sus módulos que abarrotan 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 deben 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 del subpaquete con los mismos nombres que los módulos:
subpackage_1 = package / 'module_1'
subpackage_1.mkdir()
subpackage_2 = package / 'module_2'
subpackage_2.mkdir()
Mueva las implementaciones:
package_module_1.rename(subpackage_1 / 'foo_implementation.py')
package_module_2.rename(subpackage_2 / 'Bar_implementation.py')
crear __init__.py
s para los subpaquetes que declaran el __all__
para cada:
(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 tiene la api aprovisionada a nivel de paquete:
>>> import package
>>> package.foo()
>>> package.Bar()
Y puede agregar fácilmente cosas a su API que puede administrar en el nivel del subpaquete en lugar del nivel del módulo del subpaquete. Si desea agregar un nuevo nombre a la API, simplemente actualice el __init__.py
, por ejemplo, en module_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ías 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.
Puedes cambiar eso más tarde 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 un _
. Tu ciertamente podría confiar en este mecanismo. Algunos paquetes de la biblioteca estándar de Python, de hecho, hacer confían en esto, pero para hacerlo, alias sus importaciones, por ejemplo, en ctypes/__init__.py
:
import os as _os, sys as _sys
Utilizando el _
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 respaldar indefinidamente algo que pretendía que solo fuera un detalle de implementación, solo porque se olvidó de hacerlo prefix un _
al nombrar una función.
Yo personalmente escribo un __all__
al principio de mi ciclo de vida de desarrollo de módulos para que otras personas 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 usan __all__
.
Al evitar __all__
tiene sentido
Tiene sentido ceñirse al _
prefix convención en lugar de __all__
cuando:
- Todavía estás en el modo de desarrollo inicial y no tienes usuarios, y estás constantemente ajustando tu API.
- Tal vez tenga usuarios, pero tiene pruebas unitarias que cubren la API, y todavía está agregando activamente a la API y ajustando en el desarrollo.
Un export
decorador
La desventaja de usar __all__
es que debe escribir los nombres de las funciones y clases que se exportan dos veces, y la información se mantiene separada de las definiciones. Nosotros podría use un decorador para resolver este problema.
Tuve la idea de un decorador de exportación de este tipo de la charla de David Beazley sobre envases. 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 tendrá que agregar manualmente los nombres nuevamente en el __all__
Entonces, en, por ejemplo, una biblioteca de utilidades, definiría 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 un __all__
, tu hiciste 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 si se ejecuta como principal o importado por otra función.
$ cat > run.py
import main
main.main()
$ python run.py
main
Y aprovisionamiento de API con 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
main() # expected to error here, not exported
NameError: name 'main' is not defined