Dodawanie kodu do __init__.py

85

Przyglądam się, jak działa system modelowy w django i zauważyłem coś, czego nie rozumiem.

Wiem, że tworzysz pusty __init__.pyplik, aby określić, że bieżący katalog jest pakietem. I że możesz ustawić jakąś zmienną w programie __init__.py, aby import * działał poprawnie.

Ale django dodaje kilka instrukcji from ... import ... i definiuje kilka klas w __init__.py. Czemu? Czy to nie sprawia, że ​​wszystko wygląda na bałagan? Czy istnieje powód, dla którego ten kod jest wymagany __init__.py?

Erik
źródło
13
Tak naprawdę nie chodzi o Django, prawda? Tak, widzieliście to najpierw w Django, ale wygląda to bardziej na czystą rzecz w Pythonie - może znacznik Django nie jest odpowiedni.
S.Lott,
Nie widzę żadnych instrukcji importu w __init__.pydjango 1.8. Czy to dotyczyło starszej wersji? jeśli tak, która wersja?
Gobi Dasu,

Odpowiedzi:

72

Wszystkie importy w programie __init__.pysą dostępne podczas importowania pakietu (katalogu), który go zawiera.

Przykład:

./dir/__init__.py:

import something

./test.py:

import dir
# can now use dir.something

EDYCJA: zapomniałem wspomnieć, że kod jest __init__.pyuruchamiany przy pierwszym imporcie modułu z tego katalogu. Zwykle jest to więc dobre miejsce na umieszczenie dowolnego kodu inicjującego na poziomie pakietu.

EDIT2: dgrant wskazał na możliwe zamieszanie w moim przykładzie. W __init__.py import somethingmoże importować każdego modułu, nie jest konieczne z opakowania. Na przykład możemy go zamienić na import datetime, wtedy na naszym najwyższym poziomie test.pyoba te fragmenty będą działać:

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

i

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

Najważniejsze jest to, że wszystkie nazwy przypisane w __init__.py, czy to zaimportowanych modułach, funkcjach czy klasach, są automatycznie dostępne w przestrzeni nazw pakietu za każdym razem, gdy importujesz pakiet lub moduł w pakiecie.

Alexander Kojevnikov
źródło
Ok, dzięki. Ale nadal nie jestem pewien, dlaczego dobrym pomysłem byłoby dodanie klas do. __init__.py Tak naprawdę nie biorę pod uwagę kodu inicjalizacji tych klas (ale może się mylę).
Erik,
Są to prawdopodobnie klasy przydatne za każdym razem, gdy pracujesz z pakietem. Ale nie chcę spekulować, może być wiele powodów, dla których tam są, obiektywne lub nie :)
Alexander Kojevnikov
13
Może to mieć również przyczyny historyczne. Kiedy konwertujesz module na package, module.py na module / __ init__.py cały istniejący kod może go używać tak jak poprzednio, ale teraz moduł może mieć podmoduły.
Łukasz
1
Moduły wykonują rodzica __init__.pyniejawnie. Importując moduły do ​​środka __init__.py, tworzysz cykliczne importy. __init__.pyNie zostanie wykonany w pełni przed jednym takim imporcie. Bezpieczniej jest trzymać __init__.pypuste.
Ivo Danihelka,
Należy zauważyć, że nie jest to nic specyficznego dla __init__.pyplików. Gdybyś miał plik, dir/other.pyktóry zawierał coś takiego, from datetime import datetimemógłbyś również zadzwonić dir.other.datetime.now()lub nawet from dir.other import datetime.
Carles Sala,
37

To tylko osobiste preferencje i ma związek z układem modułów w Pythonie.

Powiedzmy, że masz moduł o nazwie erikutils. Istnieją dwa sposoby, że może to być moduł, albo masz plik o nazwie erikutils.py na własną sys.pathlub masz katalog o nazwie erikutils na własną sys.pathpustym __init__.pypliku wewnątrz niego. Pozwól, że masz kilka modułów zwanych fileutils, procutils, parseutilsi chcesz te podwykonawstwa w ramach modułów mocy erikutils. Więc tworzysz kilka plików .py o nazwach fileutils.py , procutils.py i parseutils.py :

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

Może masz kilka funkcji, które po prostu nie należą w fileutils, procutilslub parseutilsmodułów. Powiedzmy, że nie masz ochoty tworzyć nowego modułu o nazwie miscutils. ORAZ chciałbyś móc wywołać funkcję w ten sposób:

erikutils.foo()
erikutils.bar()

zamiast robić

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

Więc ponieważ erikutilsmoduł jest katalogiem, a nie plikiem, musimy zdefiniować jego funkcje wewnątrz __init__.pypliku.

W django najlepszym przykładem, jaki przychodzi mi do głowy, jest django.db.models.fields. WSZYSTKIE klasy django * Field są zdefiniowane w __init__.pypliku w katalogu django / db / models / fields . Myślę, że zrobił to dlatego, że nie chce się dopchać wszystko do hipotetycznego django / db / models / fields.py modelu, więc podzielić ją na kilka submodułów ( related.py , files.py , na przykład) i utknęli utworzone * definicje pól w samym module pól (stąd, __init__.py).

dgrant
źródło
1
dgrant, chodziło mi o to, że somethingmoże to być moduł zewnętrzny, reż. coś jeszcze będzie działać. Dzięki za komentarz, zmodyfikuję swój post, aby był bardziej przejrzysty.
Alexander Kojevnikov,
29

Użycie __init__.pypliku pozwala uczynić wewnętrzną strukturę pakietu niewidoczną z zewnątrz. Jeśli struktura wewnętrzna ulegnie zmianie (np. Z powodu podzielenia jednego modułu fat na dwa), musisz tylko dostosować __init__.pyplik, ale nie kod, który zależy od pakietu. Możesz także uczynić części pakietu niewidocznymi, np. Jeśli nie są gotowe do ogólnego użytku.

Zauważ, że możesz użyć delpolecenia, więc typowe __init__.pymoże wyglądać tak:

from somemodule import some_function1, some_function2, SomeObject

del somemodule

Teraz, jeśli zdecydujesz się podzielić, somemodulenowy __init__.pymoże być:

from somemodule1 import some_function1, some_function2
from somemodule2 import SomeObject

del somemodule1
del somemodule2

Z zewnątrz opakowanie nadal wygląda dokładnie tak, jak poprzednio.

nikow
źródło
1
@Arlen: Chodzi o to, że nie jest częścią publicznego API. Jeśli zmienisz nazwę modułu, możesz być pewien, że żaden zależny kod nie zepsuje się. Dodatkowo zapewnia to, że elementy API pojawiają się tylko raz, np. Gdy introspekcja jest używana do automatycznego tworzenia dokumentacji API.
nikow
5
@Arlen: Usunięcie modułu zapobiega import <pack>.somemodule1bezpośredniemu. Można importować tylko z <pack>obiektów zdefiniowanych lub zaimportowanych w jego __init__.pyi nieusuniętych podmodułach.
MestreLion