Python 3.4: import
relativ vs. absolut
Ursprünglich implementiert Python relative imports, d.h. schreibt man im Module foo
ein import bar
, such Python 2 zunächst nach dem Modul foo.bar
.
Findet es diese nicht, sucht es dann nach bar
.
Python 3 verwendet standardmäßig absolute imports, was man auch schon in Python 2 mit einem from __future__ import absolute_import
erreichen kann.
Module können dann so importiert werden:
# absoluter import
import abso.lut
from abso import lut
# relativer import
import .rela.tiv
from .rela import tiv
# oder auch so
import ..siblling.child
from ..sibling import child
Problematisch dabei ist, dass man ab dann diese Module nicht mehr direkt aufrufen kann, also so etwas wie:
python2 /usr/lib/python2.7/dist-packages/abso/lut.py
.
Denn das Modul weiß nicht, das es eigentlich sys.modules["abso.lut"]
ist und kann deswegen die relativen Referenzen nicht auflösen.
Ein python2 -m abso.lut
dagegen funktioniert, weil Python das Modul dann eben über diesen Namen importiert und damit dann auch relative Imports auflösen kann.
Pfade dist
Mit der Umstellung von python-support
auf dh-python2
haben wir gerade mühevoll überall /usr/share/pyshared/
bzw. /usr/lib/pymodules/python2.7/
durch /usr/lib/python2.7/dist-packages/
ersetzt, mit Python 3 gibt es natürlich einen neue Pfade:
Unter /usr/lib/python3/dist-packages/
landen unsere Pakete, aber es gibt eben auch noch /usr/lib/python3**.X**/dist-packages/
für jede Version des Python 3 Interpreters.
Mit UCS-4 (= Debian Stretch) haben wir Python 3.5, UCS-5 (= Debian Buster) aber Python 3.7.
Zum Glück kann uns das derzeit aber egal sein.
Trotzdem sollten wir es vermeiden, diese Pfade überall hart zu kodieren, wie es derzeit aber leider in UCS manchmal noch der Fall ist:
# Bad
/usr/bin/python2.7 /usr/lib/pymodules/python2.7/univention/s4connector/s4/main.py
# Good
python2.7 -m univention.s4connector.s4.main
Aber auch hier steckt der Teufel im Detail und man muss jeden Fall genau anschauen, denn manchmal funktioniert es so einfach dann doch nicht:
Wegen relative Imports werden dann plötzlich Module nicht mehr gefunden oder der Code kommt nicht mit dem geänderten __main__
klar.
Pfade src
Die Umstellung auf relative Imports wird zusätzlich bei uns in UCS dadurch erschwert, dass wir einen Wildwuchs von unterschiedlichen Strukturen in unseren Qullpaketen haben:
- Die installierten Module von
univention.lib
liegen im Quellcode aber unterpython/
. - Beim Listener dagegen gibt es im Quelllcode bereits das Verzeichnis
python/
, unter dem dann die Dateien „schon an der richtigen Stelle“ liegen. - Bei
management/univention-management-console
heißt das Verzeichnis dagegensrc/
. - Bei
base/univention-ipcalc
liegt dasunivention/
-Verzeichnis direkt unterhalb der Wurzel. - Bei
base/univention-python
auch direkt unterhalb der Wurzel, werden aber nachunivention
installiert.
Diese unterschiedlichen Pfade haben schon bei der Generierung der Python API Dokumentation zu Problemen geführt.
Eine Vereinheitlichung halte ich für sehr sinnvoll, den mit der aktuellen Struktur ist ein Aufruf der Module über python -m
nicht möglich.
Current best practice
Unterhalb eines
src/
-Verzeichnisses liegen die Python-Dateien an der Stelle, an der sie dann später auch installiert werden. Das Quellcodepaket beinhaltet enthält in allen Verzeichnissen eine__init__.py
mit dempkgutil.extend_path
-Fraagment, um zu gewährleisten, das die die Pakete gegenseitig ergänzen können. Diese zusätzlichen__init__.py
-Dateien werden aber nicht installiert und sind damit nicht Bestandteil des Binärpakete.
Grund für das zusätzliche src/
-Unterverzeichnis ist, dass standardmäßig das aktuelle Arbeitsverzeichnis .
in sys.path
an erster Stelle eingefügt wird.
Befindet man sich also im Wurzelverzeichnis eines Quellpakets, würden die Python-Module darin also direkt verwendet werden, wenn sie direkt darin liegen würden.
Das ist nicht immer sinnvoll und führt unfreiwillig zu Problemen.
Deswegen lautet die Empfehlung, alles unterhalb eines Unterverzeichnisses src/
abzulegen;
man kann dann dieses Verzeichnis entweder explizit über PYTHONPATH=src
aufnehmen oder wechselt explizit in das src/
-Verzeichnis, damit dieses dann über den .
-Mechanismus aufgenommen wird.