Vom Sprachdesign: Konstruktoren

written by Martin HĂ€cker on

Endlich ist mir wieder so ein Punkt klargeworden, der mich schon lange an Python stört, den ich aber bisher noch nicht so richtig spezifizieren konnte.

Zusammenfassen lÀsst es sich als: Konvention ist besser als eine eigene Syntax / Regel.

Konkret geht es um folgendes: Python besitzt eine eigene Syntax fĂŒr Konstruktoren. NĂ€mlich diese:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

point = Point(10, 15)

Der Konstruktor eines Objekts heißt immer __init__ (das mit den Unterstrichen finde ich sowieso hĂ€sslich, aber well, darĂŒber Ă€rgere ich mich wan anderes).

Das ist gut, weil man so den Konstruktor immer gleich findet.

ABER: Das ist total scheiße wenn man mehrere Wege braucht um ein Objekt zu initialisieren.

Denn genau wie in Java auch, kann man anhand den Argumenttypen ja keinen dispatch machen. Doh.

Also, wenn man das Beispiel erweitern will um auch Polarkoordinaten zu unterstĂŒtzen, dann geht das nicht, denn bei

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __init__(self, theta, phi)
        self.x = ...
        self.y = ...

point = Point(10, 15)

kann die Runtime nicht mehr auflösen welchen Konstruktor mann denn jetzt genau gemeint hat.

Doh.

Jetzt hat man entweder die Wahl so etwas zu machen wie hier im Trac-Projekt

class WikiPage(object):
    """Represents a wiki page (new or existing)."""

    realm = 'wiki'

    def __init__(self, env, name=None, version=None, db=None):
        self.env = env
        if isinstance(name, Resource):
            self.resource = name
            name = self.resource.id
        else:
            self.resource = Resource('wiki', name, version)
        self.name = name
# ....

Was aber total ekelig ist, denn jetzt hat der Konstruktor auf einmal einen Parameter name der ĂŒberhaupt nicht mehr dass ist, was er mit seinem Namen dokumentiert.

Klasse. Jetzt muss man tatsĂ€chlich den Code des Konstruktors lesen um zu verstehen was fĂŒr Argumente man benötigt um so ein Objekt zu erzeugen. Habt ihr wirklich gut gemacht!!!!11!!

Oder aber man scheißt auf das Konzept eines Konstruktors und schreibt Factory-Methoden, also im Beispiel etwa so:

class WikiPage(object):
    """Represents a wiki page (new or existing)."""

    realm = 'wiki'

    @classmethod()
    def create_from_resource(klass, env, resource, db=None):
        page = WikiPage(env, resource.id, resource.version, db)
        page.resource = resource
        return page

    def __init__(self, env, name=None, version=None, db=None):
        self.env = env
        self.resource = Resource('wiki', name, version)
        self.name = name
# ....

Das ist jetzt immerhin klarer - hat aber den Nachteil das der Leser jetzt wissen muss dass man Factory Methoden einsetzt und welche (nichtstandardisierte) Konvention man fĂŒr diese einsetzt. Na Klasse.

Ergo: Die Sprachdesigner haben sich gedacht, das es ja eine Tolle Sache wĂ€re, wenn sie eine eigene Syntax einfĂŒhren wĂŒrden, damit Konstruktoren etwas besonderes sein können und haben damit erreicht, dass man sich beim tatsĂ€chlichen Programmieren jetzt auf einmal ein Bein Brechen muss, damit man ganz alltĂ€gliche Probleme auf eine saubere Art und Weise lösen kann.

Ganz groß.

HĂ€tte man doch von vornherein anerkannt, das ein Konstruktor auch nur eine Methode ist - mit der Konvention dass der Name mit "init" anfĂ€ngt - dann existierten auf einmal diese Probleme nicht mehr. Denn dann kann man ĂŒber den namen der Methode dokumentieren was sie soll.

class Point:

    def init_cartesian(self, x, y):
        self.x = x
        self.y = y
        return self

    def init_polar(self, theta, phi):
        # ...
        return self

cartesian = Point().init_cartesian(10, 15)
polar = Point().init_polar(0.23, 0.23)

Yucheh!

Dazu kommt, dass man dann auf einmal sinnige Dinge tun kann, wie zum Beispiel, das ein "Konstruktor" nicht immer die gleiche Klasse zurĂŒckgeben muss. Man könnte also zum Beispiel eine öffentliche Superklasse haben, die aber tatsĂ€chlich in mehreren privaten Subklassen implementiert ist - zum Beispiel ein String, der je nachdem wie er erzeugt wird anders aussieht: Stringkonstanten vielleicht eine Klasse die nicht/anders an der Garbage-Collection teilnimmt und dafĂŒr Uniqued (also garantiert nur einmal) existieren (fĂŒr einen schnelleren Interpreter interessant), Strings aus einer Datei entweder normal, oder falls die Datei eine gewisse GrĂ¶ĂŸe ĂŒberschreitet als Memory-Mapped File, Strings die Pfade reprĂ€sentieren so, dass sie mit den Eigenheiten von Dateisystemen besser klarkommen, Strings die keine Unicode enthalten noch mal anders, damit sie effizienter sein können.... etc.

Und das alles, weil man auf Sprachebene auf ein zusÀtzliches Konzept verzichtet.

Aber dass, scheint ja fĂŒr Sprachdesigner das Schwerste zu sein.