Añadiendo código a __init__.py

85

Estoy echando un vistazo a cómo funciona el sistema modelo en django y noté algo que no entiendo.

Sé que crea un __init__.pyarchivo vacío para especificar que el directorio actual es un paquete. Y que puede establecer alguna variable __init__.pypara que la importación * funcione correctamente.

Pero django agrega un montón de declaraciones from ... import ... y define un montón de clases en __init__.py. ¿Por qué? ¿No hace esto que las cosas se vean desordenadas? ¿Hay alguna razón por la que se requiera este código __init__.py?

Erik
fuente
13
No se trata realmente de Django, ¿verdad? Sí, lo viste primero en Django, pero esto parece más una cosa pura de Python, tal vez la etiqueta de Django no sea realmente apropiada.
S.Lott
No veo ninguna declaración de importación en __init__.pydjango 1.8. ¿Fue esto para una versión anterior? si es así, ¿qué versión?
Gobi Dasu

Respuestas:

72

Todas las importaciones en __init__.pyestán disponibles cuando importa el paquete (directorio) que lo contiene.

Ejemplo:

./dir/__init__.py:

import something

./test.py:

import dir
# can now use dir.something

EDITAR: olvidé mencionar, el código se __init__.pyejecuta la primera vez que importa cualquier módulo de ese directorio. Por lo tanto, normalmente es un buen lugar para colocar cualquier código de inicialización a nivel de paquete.

EDIT2: dgrant señaló una posible confusión en mi ejemplo. En __init__.py import somethingpuede importar cualquier módulo, no es necesario del paquete. Por ejemplo, podemos reemplazarlo con import datetime, luego, en nuestro nivel superior, test.pyestos dos fragmentos funcionarán:

import dir
print dir.datetime.datetime.now()

y

import dir.some_module_in_dir
print dir.datetime.datetime.now()

La conclusión es: todos los nombres asignados __init__.py, ya sean módulos, funciones o clases importados, están disponibles automáticamente en el espacio de nombres del paquete cada vez que importa el paquete o un módulo en el paquete.

Alexander Kojevnikov
fuente
Bien gracias. Pero todavía no estoy seguro de por qué sería una buena idea agregar clases __init__.py . Realmente no considero el código de inicialización de estas clases (pero tal vez me equivoque al respecto).
Erik
Estas son probablemente las clases que son útiles cada vez que trabaja con el paquete. Pero no quiero especular, podría haber muchas razones por las que están allí, objetivos o no :)
Alexander Kojevnikov
13
Esto también puede deberse a razones históricas. Cuando está convirtiendo módulo a paquete, module.py a módulo / __ init__.py, todo el código existente puede usarlo como antes, pero ahora el módulo puede tener submódulos.
Łukasz
1
Los módulos se ejecutan al padre __init__.pyimplícitamente. Al importar los módulos internos __init__.py, está creando importaciones cíclicas. El __init__.pyno se ejecutará por completo antes de uno de tales importaciones. Es más seguro mantenerlo __init__.pyvacío.
Ivo Danihelka
Es importante señalar que esto no es nada específico de los __init__.pyarchivos. Si tuviera un archivo dir/other.pyque tuviera algo como from datetime import datetime, también podría llamar dir.other.datetime.now()o incluso from dir.other import datetime.
Carles Sala
37

En realidad, es solo una preferencia personal y tiene que ver con el diseño de sus módulos de Python.

Digamos que tiene un módulo llamado erikutils. Hay dos formas en que puede ser un módulo, o tiene un archivo llamado erikutils.py en su sys.patho tiene un directorio llamado erikutils en su sys.pathcon un __init__.pyarchivo vacío dentro. Entonces digamos que usted tiene un montón de módulos llamados fileutils, procutils, parseutilsy que desea que estén bajo submóduloserikutils . Entonces crea algunos archivos .py llamados fileutils.py , procutils.py y parseutils.py :

erikutils
  __init__.py
  fileutils.py
  procutils.py
  parseutils.py

Tal vez usted tiene algunas funciones que simplemente no tienen cabida en los fileutils, procutilso parseutilsmódulos. Y digamos que no tiene ganas de crear un nuevo módulo llamado miscutils. Y, le gustaría poder llamar a la función así:

erikutils.foo()
erikutils.bar()

en lugar de hacer

erikutils.miscutils.foo()
erikutils.miscutils.bar()

Entonces, debido a que el erikutilsmódulo es un directorio, no un archivo, tenemos que definir sus funciones dentro del __init__.pyarchivo.

En django, el mejor ejemplo en el que puedo pensar es django.db.models.fields. TODAS las clases de django * Field se definen en el __init__.pyarchivo en el directorio django / db / models / fields . Supongo que hicieron esto porque no querían meter todo en un modelo hipotético de django / db / models / fields.py , por lo que lo dividieron en algunos submódulos ( related.py , files.py , por ejemplo) y pegaron las definiciones de * Field hechas en el módulo de campos en sí (por lo tanto, __init__.py).

dgrant
fuente
1
dgrant, lo que quise decir es que somethingpuede ser un módulo externo, dir. algo funcionará. Gracias por el comentario, editaré mi publicación para que quede más claro.
Alexander Kojevnikov
29

El uso del __init__.pyarchivo le permite hacer que la estructura interna del paquete sea invisible desde el exterior. Si la estructura interna cambia (por ejemplo, porque divide un módulo fat en dos) solo tiene que ajustar el __init__.pyarchivo, pero no el código que depende del paquete. También puede hacer que partes de su paquete sean invisibles, por ejemplo, si no están listas para uso general.

Tenga en cuenta que puede usar el delcomando, por lo que un típico __init__.pypuede verse así:

from somemodule import some_function1, some_function2, SomeObject

del somemodule

Ahora bien, si decides dividir somemoduleel nuevo __init__.pypodría ser:

from somemodule1 import some_function1, some_function2
from somemodule2 import SomeObject

del somemodule1
del somemodule2

Desde el exterior, el paquete todavía se ve exactamente como antes.

nikow
fuente
1
@Arlen: El caso es que no forma parte de la API pública. Si cambia el nombre de un módulo, puede estar seguro de que ningún código dependiente se rompe. Además, esto garantiza que los elementos de la API solo aparezcan una vez, por ejemplo, cuando se utiliza la introspección para crear automáticamente la documentación de la API.
nikow
5
@Arlen: Eliminar un módulo evita que uno lo haga import <pack>.somemodule1directamente. Solo puede importar desde <pack>objetos definidos o importados en sus __init__.pysubmódulos y no eliminados.
MestreLion