Ajout de code à __init__.py

85

Je regarde comment fonctionne le système de modèles dans django et j'ai remarqué quelque chose que je ne comprends pas.

Je sais que vous créez un __init__.pyfichier vide pour spécifier que le répertoire actuel est un package. Et que vous pouvez définir une variable __init__.pypour que l'importation * fonctionne correctement.

Mais django ajoute un tas d'instructions from ... import ... et définit un tas de classes dans __init__.py. Pourquoi? Est-ce que cela ne rend pas les choses désordonnées? Y a-t-il une raison pour laquelle ce code est requis __init__.py?

Erik
la source
13
Ce n'est pas vraiment Django, n'est-ce pas? Oui, vous l'avez vu pour la première fois dans Django, mais cela ressemble plus à du pur Python - peut-être que la balise Django n'est pas vraiment appropriée.
S.Lott
Je ne vois aucune instruction d'importation dans __init__.pydjango 1.8. Était-ce pour une version plus ancienne? si oui quelle version?
Gobi Dasu

Réponses:

72

Toutes les importations dans __init__.pysont rendues disponibles lorsque vous importez le package (répertoire) qui le contient.

Exemple:

./dir/__init__.py:

import something

./test.py:

import dir
# can now use dir.something

EDIT: j'ai oublié de mentionner, le code __init__.pys'exécute la première fois que vous importez un module de ce répertoire. C'est donc normalement un bon endroit pour mettre tout code d'initialisation au niveau du package.

EDIT2: dgrant a signalé une possible confusion dans mon exemple. Dans __init__.py import somethingpeut importer n'importe quel module, pas nécessaire à partir du package. Par exemple, nous pouvons le remplacer par import datetime, puis dans notre niveau supérieur, test.pyces deux extraits fonctionneront:

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

et

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

En bout de ligne, tous les noms attribués dans __init__.py, qu'il s'agisse de modules, de fonctions ou de classes importés, sont automatiquement disponibles dans l'espace de noms du package chaque fois que vous importez le package ou un module dans le package.

Alexander Kojevnikov
la source
D'accord merci. Mais je ne sais toujours pas pourquoi ce serait une bonne idée d'ajouter des classes.Je __init__.py ne considère pas vraiment le code d'initialisation de ces classes (mais peut-être que je me trompe).
Erik
Ce sont probablement les classes qui sont utiles chaque fois que vous travaillez avec le package. Mais je ne veux pas spéculer, il pourrait y avoir de nombreuses raisons pour lesquelles ils sont là, objectifs ou non :)
Alexander Kojevnikov
13
Cela peut également être pour des raisons historiques. Lorsque vous convertissez un module en package, module.py en module / __ init__.py, tout le code existant peut l'utiliser comme auparavant, mais maintenant le module peut avoir des sous-modules.
Łukasz
1
Les modules exécutent __init__.pyimplicitement le parent . En important les modules à l'intérieur __init__.py, vous créez des importations cycliques. Le __init__.pyne sera pas entièrement exécuté avant une telle importation. Il est plus sûr de rester __init__.pyvide.
Ivo Danihelka
Il est important de noter que cela n'a rien de spécifique aux __init__.pyfichiers. Si vous aviez un fichier dir/other.pyqui contenait quelque chose comme from datetime import datetimevous pourriez également appeler dir.other.datetime.now()ou même from dir.other import datetime.
Carles Sala
37

C'est juste une préférence personnelle, et cela a à voir avec la disposition de vos modules python.

Disons que vous avez un module appelé erikutils. Il y a deux façons dont il peut être un module, soit vous avez un fichier appelé erikutils.py sur votre sys.pathou vous avez un répertoire appelé erikutils sur votre sys.pathavec un __init__.pyfichier vide à l'intérieur. Alors disons que vous avez un tas de modules appelés fileutils, procutils, parseutilset vous voulez que ces sous- traiter les modules sous erikutils. Vous créez donc des fichiers .py appelés fileutils.py , procutils.py et parseutils.py :

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

Peut-être vous avez quelques fonctions qui juste ne appartiennent à la fileutils, procutilsou des parseutilsmodules. Et disons que vous n'avez pas envie de créer un nouveau module appelé miscutils. ET, vous aimeriez pouvoir appeler la fonction comme ceci:

erikutils.foo()
erikutils.bar()

plutôt que de faire

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

Donc, comme le erikutilsmodule est un répertoire, pas un fichier, nous devons définir ses fonctions à l'intérieur du __init__.pyfichier.

Dans django, le meilleur exemple auquel je puisse penser est django.db.models.fields. TOUTES les classes de champ django * sont définies dans le __init__.pyfichier du répertoire django / db / models / fields . Je suppose qu'ils l'ont fait parce qu'ils ne voulaient pas tout entasser dans un modèle hypothétique django / db / models / fields.py , ils l'ont donc divisé en quelques sous-modules ( related.py , files.py , par exemple) et ils ont collé les définitions * de champ faites dans le module de champs lui-même (par conséquent, __init__.py).

dégagés
la source
1
dgrant, ce que je voulais dire, c'est que somethingpeut être un module externe, dir.quelque chose fonctionnera toujours. Merci pour le commentaire, je modifierai mon message pour le rendre plus clair.
Alexander Kojevnikov le
29

L'utilisation du __init__.pyfichier vous permet de rendre la structure interne du package invisible de l'extérieur. Si la structure interne change (par exemple parce que vous divisez un gros module en deux), vous n'avez qu'à ajuster le __init__.pyfichier, mais pas le code qui dépend du package. Vous pouvez également rendre des parties de votre package invisibles, par exemple si elles ne sont pas prêtes pour une utilisation générale.

Notez que vous pouvez utiliser la delcommande, donc un exemple typique __init__.pypeut ressembler à ceci:

from somemodule import some_function1, some_function2, SomeObject

del somemodule

Maintenant, si vous décidez de diviser somemodulele nouveau __init__.pypourrait être:

from somemodule1 import some_function1, some_function2
from somemodule2 import SomeObject

del somemodule1
del somemodule2

De l'extérieur, le colis est toujours exactement comme avant.

nikow
la source
1
@Arlen: Le fait est qu'il ne fait pas partie de l'API publique. Si vous renommez un module, vous pouvez être sûr qu'aucun code dépendant n'est interrompu. De plus, cela garantit que les éléments de l'API n'apparaissent qu'une seule fois, par exemple, lorsque l'introspection est utilisée pour créer automatiquement la documentation de l'API.
nikow
5
@Arlen: La suppression d'un module en empêche un import <pack>.somemodule1directement. Vous ne pouvez importer qu'à partir d' <pack>objets définis ou importés dans ses __init__.pysous-modules et non supprimés.
MestreLion