Hinweis: Obwohl JavaScript für diese Website nicht unbedingt erforderlich ist, werden Ihre Interaktionsmöglichkeiten mit den Inhalten eingeschränkt sein. Bitte aktivieren Sie JavaScript für das volle Erlebnis.

Python Distutils-SIG: Design Proposal

Python Distutils-SIG

(Voraussetzungen: Bitte lesen Sie die vorgeschlagene Schnittstelle, bevor Sie versuchen, dieses Designdokument durchzuarbeiten; es ist sehr stark eine Fortsetzung des Schnittstellendokuments.)

Design Proposal

Der Blickwinkel von Distutils

setup.py muss nur ein Modul importieren: distutils.core. Dieses Modul ist verantwortlich für das Parsen aller Kommandozeilenargumente an setup.py (obwohl die Interpretation von Optionen über die verschiedenen Distutils-Befehle und möglicherweise das Client-setup.py verteilt ist). Es kümmert sich auch um den Empfang der Steuerung von setup.py und leitet sie entsprechend an die Distutils-Befehle weiter. Am wichtigsten ist, dass distutils.core die Distribution-Klasse definiert, die das Herz und die Seele von Distutils ist. Der Client (setup.py) existiert hauptsächlich, um Attribute für eine Distribution-Instanz bereitzustellen, und alle Distutils-Befehle arbeiten mit dieser Instanz. distutils.core definiert auch die Command-Klasse, die sich bei der Implementierung von Distutils-Befehlen als nützlich erweist.

Apropos Distutils-Befehle: Jeder wird als Python-Modul implementiert, z.B. der build-Befehl wird durch das Modul distutils.build implementiert. Jedes Befehlsmodul muss eine Klasse definieren, die ebenfalls nach dem Befehl benannt ist – z.B. distutils.build.Build. Diese Befehlsklassen erben von der Command-Klasse, die (zumindest) eine Möglichkeit bietet, mit befehlspezifischen Optionen umzugehen. (Command stellt einen Konstruktor bereit, der eine Distribution-Klasse und eine optionale Liste von Argumenten für diesen Befehl entgegennimmt und die Argumentliste durch Inspektion von getopt-ähnlichen Optionsspezifizierern in der von Command abgeleiteten Instanz parst.) Jede Befehlsklasse muss eine Methode run bereitstellen, die die Informationen in der Distribution-Instanz und die Befehlsoptionen verwendet, um "ihre Sache zu tun". Gut geschriebene Befehlsklassen werden diese Aufgabe in mehrere gut definierte (und dokumentierte) Methoden aufteilen, so dass der Client setup.py von einer Distutils-Befehlsklasse erben und spezifische Verhaltensweisen überschreiben kann. Das bedeutet auch, dass die Distribution-Klasse eine Möglichkeit haben muss, überschriebene Befehlsklassen an den Hauptdispatcher zu kommunizieren.

Der Blickwinkel des Clients

Wie bereits erwähnt, muss der Client (setup.py) nur distutils.core importieren – alles andere Distutils-bezogene wird von diesem Kernmodul übernommen. Der Client benötigt jedoch eine Möglichkeit, seine spezifischen Optionen an den Distutils-Kern (und an die Befehlsmodule) zu kommunizieren.

Dafür gibt es zwei mögliche Schemata: eines kurz und praktisch (aber nicht sehr erweiterbar) und das andere etwas wortreich und umständlich (aber objektorientierter und erweiterbarer). Es gibt keinen Grund, warum wir nicht beides haben können; die praktische Schnittstelle könnte einfach ein Wrapper um die vollwertige Schnittstelle für die vielen Modulverteilungen sein, die keine umfangreichen Anpassungen benötigen.

Zuerst hier ein Beispiel für die einfache Schnittstelle, verwendet für eine Modulverteilung mit einem einzelnen "reinen Python"-Modul (mymod.py).

    from distutils.core import setup
    setup (name = "mymod",
           version = "1.2",
           author = "Greg Ward <gward@cnri.reston.va.us>",
           description = "A very simple, one-module distribution")
Beachten Sie, dass wir mymod.py nirgendwo explizit auflisten: Distutils geht davon aus, dass es sich um eine Ein-Modul-Verteilung handelt, die nach ihrem einzigen Modul benannt ist (mymod).

Diejenigen, die gerne Unterklassen definieren, werden es vielleicht vorziehen, dies anders zu formulieren

    from distutils.core import Distribution, setup

    class MyDistribution (Distribution):
        name = "mymod"
        version = "1.2",
        author = "Greg Ward <gward@cnri.reston.va.us>",
        description = "A very simple, one-module distribution")

    setup (distclass = MyDistribution)
Das ist für eine kleine Verteilung übertrieben: Wir definieren eine neue Klasse nur, um Attributwerte bereitzustellen, wenn distutils.core.setup hauptsächlich existiert, damit wir dies sowieso tun können. Dennoch werden OO-Puristen dies mögen – und zweifellos wird es Zeiten geben, in denen der Client Verhalten überschreiben muss, nicht nur Daten, und die OO-Schnittstelle notwendig sein wird.

Und komplexere Modulverteilungen mit vielen zu anpassenden Attributen sind möglicherweise leichter zu lesen/zu warten, wenn die Dinge so aufgeteilt sind. Betrachten Sie eine Verteilung mit zwei reinen Python-Modulen (mymod und my_othermod) und einer C-Erweiterung (myext); die C-Erweiterung muss mit zwei zusätzlichen C-Dateien und einer C-Bibliothek verknüpft werden. Ach ja, diese Verteilung erfordert Python 1.5 und jede Version des re-Moduls (ignoriert die Tatsache, dass das eine das andere normalerweise impliziert)

    from distutils.core import Distribution, setup

    class MyDistribution (Distribution):
        name = "mydist",
        version = "1.3.4",
        author = "Greg Ward <gward@cnri.reston.va.us>"
        description = """\
    This is an example module distribution.  It provides no useful code,
    but is an interesting example of the Distutils in action."""

        # Dependencies
        requires = { 'python': '1.5',  # I like class-based exceptions
                     're': '*',        # and I love Perl-style regexps! ;-)
                   }
        # Actual files that need to be processed and installed in some form
        py_modules = ['mymod.py', 'my_othermod.py'],
        ext_modules = {'myext.c': 
                        {'other_c': ['extra1.c', 'extra2.c'],
                         'c_libraries': ['mylib']}
                      }

    setup (distclass = MyDistribution)

Ein paar Dinge zu beachten

  • Ich habe keine Angst, tief verschachtelte Datenstrukturen zu verwenden; wenn Sie Python-Module schreiben und verteilen, sollte das kein Problem sein!
  • jedes Attribut hat einen bestimmten Typ (String, Liste, Wörterbuch, ...)
  • die Attribute mit komplexen Typen (insbesondere Wörterbücher) haben eine bekannte und gut dokumentierte interne Struktur, z.B.
        """ext_modules is a hash mapping names of C source files (each
        containing a Python extension module) to a nested hash of
        information about how to build that module.  The allowed keys to
        this nested hash are: 
          - other_c: other C files that must be compiled and linked with 
                     the main C file to create the module
          - c_libraries: C libraries that must be included in the link
          ...
       """
    

Zweifellos hätten die verschachtelten Hashes von ext_modules mehr Optionen, und zweifellos hätten andere Distribution-Attribute eine komplexe, dokumentierte Struktur.

Schließlich muss die Liste aller Distribution-Attribute bekannt und gut dokumentiert sein! Diese scheinen in einige breite Kategorien zu fallen. Hier ist ein erster Versuch einer Liste

  • Distributions-Metadaten
    • name
    • version
    • author
    • description
  • Abhängigkeiten
    • requires
  • Zu verarbeitende und zu installierende Dateien
    • py_modules
    • ext_modules
    • doc_files
  • Build-Verzeichnisse (standardmäßig alle unter ./blib)
    • build_lib – wo plattformunabhängige Bibliotheksdateien abgelegt werden sollen
    • build_platlib – wo plattformabhängige Bibliotheksdateien abgelegt werden sollen
    • build_exe – wo ausführbare Programme (d.h. Skripte) abgelegt werden sollen
    • build_html – wo verarbeitete Dokumentation (HTML) abgelegt werden soll
  • Installationsverzeichnisse (standardmäßig unter sysconfig.LIBDEST)
    • install_lib
    • install_platlib
    • install_exe
    • install_html
... nun, das ist ein Anfang.

Der Blickwinkel von Distutils im Rückblick

Zusammenfassend lässt sich sagen, dass wir durchgehen, was passiert, wenn der Benutzer setup.py ausführt. Ob setup.py in der einfachen (Funktionsaufruf) oder allgemeinen (Unterklasse definieren) Form geschrieben ist, spielt keine große Rolle, daher werde ich die Dinge nicht in zwei Ströme aufteilen.

  • setup.py importiert distutils.core
  • Der Startcode von distutils.core parst Kommandozeilenargumente: verarbeitet globale Optionen, die ihm bekannt sind, und speichert den Rest für den Client (setup.py) zur Verarbeitung; ermittelt die Befehle und Optionen für jeden Befehl und speichert sie alle für die spätere Verarbeitung
  • setup.py ruft distutils.core.setup auf (möglicherweise mit einem Argument distclass, das eine Unterklasse von Distribution angibt, wahrscheinlich mit vielen anderen benannten Argumenten, die verschiedene Attribute für die Distribution-Instanz angeben)
  • distutils.core.setup instanziiert Distribution (oder die vom Client bereitgestellte Unterklasse) und verwendet ihre Argumente (außer distclass), um Attribute dieser Instanz zu überschreiben
  • distutils.core.setup lädt das Befehlsmodul (z.B. distutils.build)
  • distutils.core.setup ermittelt die Befehlsklasse (normalerweise einfach nach dem Befehl benannt, z.B. distutils.build.Build, aber möglicherweise eine vom Client als eines der Attribute der Distribution-Instanz bereitgestellte Klasse) und instanziiert sie
  • Der Konstruktor der Befehlsklasse nimmt als Argumente die Distribution-Instanz und alle spezifischen Kommandozeilenargumente für diesen Befehl auf der Kommandozeile von setup.py entgegen
  • Der Konstruktor der Befehlsklasse parst seine Optionen, um einige Instanzattribute festzulegen/zu überschreiben
  • distutils.core.setup ruft die Methode run auf dem Befehlsobjekt auf
  • diese Methode tut, was auch immer der Befehl tun soll: Module erstellen, Dokumentation verarbeiten, Dateien installieren usw.
  • distutils.core.setup ermittelt die nächste Befehlsklasse (falls mehrere Befehle angegeben wurden) und fährt wie zuvor fort