¿Por qué puedo importar con éxito sin __init__.py?

¿Cuál es exactamente el uso de __init__.py ? Sí, ya sé que este archivo convierte un directorio en un paquete importable. Sin embargo, considere el siguiente ejemplo:

 project/ foo/ __init__.py a.py bar/ b.py 

Si quiero importar a en b , tengo que agregar la siguiente statement:

 sys.path.append('/path_to_foo') import foo.a 

Esto se ejecutará correctamente con o sin __init__.py . Sin embargo, si no hay una instrucción sys.path.append , se producirá un error de “no módulo”, con o sin __init__.py . Esto hace que parezca que solo la ruta del sistema es importante, y que __init__.py no tiene ningún efecto.

¿Por qué funcionaría esta importación sin __init__.py ?

__init__.py no tiene nada que ver con si Python puede encontrar su paquete. Ha ejecutado su código de manera tal que su paquete no está en la ruta de búsqueda de manera predeterminada, pero si lo hubiera ejecutado de manera diferente o configurado su PYTHONPATH diferente, sys.path.append hubiera sido innecesario.

__init__.py solía ser necesario para crear un paquete, y en la mayoría de los casos, todavía debe proporcionarlo. Sin embargo, desde Python 3.3, una carpeta sin un __init__.py puede considerarse parte de un paquete de espacio de nombres implícito , una característica para dividir un paquete en varios directorios.

Durante el proceso de importación, la maquinaria de importación continuará iterando sobre cada directorio en la ruta principal como lo hace en Python 3.2. Mientras busca un módulo o paquete llamado “foo”, para cada directorio en la ruta principal:

  • Si se encuentra /foo/__init__.py , se importa y se devuelve un paquete normal.
  • Si no, pero se encuentra /foo.{py,pyc,so,pyd} , se importa un módulo y se devuelve. La lista exacta de extensiones varía según la plataforma y si se especifica el distintivo -O. La lista aquí es representativa.
  • Si no, pero se encuentra /foo y es un directorio, se registra y el análisis continúa con el siguiente directorio en la ruta principal.
  • De lo contrario, el escaneo continúa con el siguiente directorio en la ruta principal.

Si la exploración se completa sin devolver un módulo o paquete, y se registró al menos un directorio, se creará un paquete de espacio de nombres.

Si quiero importar a en b, tengo que agregar la siguiente statement:

¡No! Solo dirías: import foo.a Todo esto se proporciona siempre que ejecute todo el paquete a la vez usando python -m main.module donde main.module es el punto de entrada a toda su aplicación. Importa todos los demás módulos, y los módulos que importan más módulos intentarán buscarlos desde la raíz de este proyecto. Por ejemplo, foo.bar.c se importará como foo.bar.b

Entonces parece que solo importa la ruta del sistema y init .py no tiene ningún efecto.

sys.path modificar sys.path solo cuando esté importando módulos de ubicaciones que no están en su proyecto o los lugares donde Python busca bibliotecas. __init__.py no solo hace que una carpeta se vea como un paquete, también hace algunas cosas más como “exportar” objetos al mundo exterior ( __all__ )

Si realmente quieres evitar __init__.py por alguna razón, no lo haces sys.path . En su lugar, cree un objeto de módulo y establezca su __path__ a una lista de directorios.

Cuando importas algo tiene que:

  1. Recuperar un módulo ya cargado o
  2. Cargar el módulo que se importó.

Cuando import foo y python encuentre una carpeta llamada foo en una carpeta en su sys.path , buscará en esa carpeta un __init__.py para que se considere el módulo de nivel superior.

(Tenga en cuenta que si el paquete no está en su sys.path , deberá append su ubicación para poder importarlo).

Si no está presente, buscará una __init__.pyc posiblemente en la carpeta __pycache__ , si eso también falta, entonces esa carpeta foo no se considera un paquete de Python que se pueda cargar. Si no se encuentran otras opciones para foo se ImportError un ImportError .

Si intenta eliminar el __init__.pyc , verá que el script de inicialización para un paquete es realmente necesario.