35. Introspektion

Wörter müssen wir jetzt als Folgen von Symbolen darstellen. Ein Wort soll entweder aus einem String (in einem noch zu definierenden Format) oder aus einem anderen Wort (das dann kopiert wird) erzeugbar sein, der Konstruktor muss also beides können.

class Word:
  def __init__(self, word):
    self.symList = []
    if isinstance(word, Word):
      self.symList = word.symList[:]
    else:
      self._parseRaw(word)
  ...

Der vollständige Quellcode für die Klasse findet sich im Anhang der Seite.

Die eingebaute Funktion isinstance prüft, ob ihr erstes Argument (im Regelfall eine Variable) eine Instanz der zweiten (im Regelfall eine Klasse) ist.

Damit:

>>> from Word import Word
>>> w = Word("Do Re Mi")
>>> w2 = Word(w)
>>> import Symbol
>>> w2.append(symbol.NonTerminal("ba"))
>>> w, w2
(Word('Do Re Mi'), Word('Do Re Mi ba'))

Das Operieren mit Namen und Typen zur Laufzeit heißt auch Introspektion. In Python wirkt das trivial, weil auch Klassen (und nicht nur Objekte) an Funktionen übergeben werden können (Dinge, die an Funktionen übergeben und von Funktionen zurückgegeben werden können, nennt man gerne first class).

(Weiterführend:) In kompilierten Sprachen ist Introspektion meistens eher kompliziert, weil bereits der Compiler die Namen verarbeitet und Typen, Klassen und Funktionen in maschinengerechte Form verpackt. Im Kompilat (das ja nur Maschinencode ist) sind im Regelfall diese Informationen nicht mehr (oder nur sehr indirekt) vorhanden und müssen, wenn man Introspektion treiben möchte, sozusagen künstlich wieder eingebaut werden.

Die Funktion type kennen wir schon. Für selbst definierte Klassen gibt type immer instance zurück.

Für eingebaute Typen sollte man (noch) auf type zurückgreifen. Das könnte so aussehen:

examples> cat verbosetype.py
class _Empty: pass
_typedict = {type([]): "e Liste",
  type(""): " String", type(1): "e ganze Zahl",
  type(_Empty()): " Objekt"}

def verboseType(ob):
  return "ein%s"%_typedict.get(type(ob),
    " anderes Ding")

und könnte so benutzt werden:

>>> from verbosetype import verboseType
>>> verboseType(4)
'eine ganze Zahl'
>>> verboseType("bla")
'ein String'
>>> verboseType(verboseType)
'ein anderes Ding'

(Weiterführend:) Bis Python 2.1 waren die eingebauten Typen keine richtigen Klassen. Das hat sich in 2.2 geändert, mit dem Preis der zusätzlichen Konfusion, dass es “alte” und “neue” Klassen gibt. Die neuen Klassen erben alle von der Klasse object. Die eingebauten Funktionen str und Freunde sind inzwischen eigentlich Klassen (die Aufrufe sind also Aufrufe von Konstruktoren).

In Python 2.0 etwa ist noch:

>>> isinstance("bla", str)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: second argument must be a class

Ab Python 2.2 ist:

>>> isinstance("bla", str)
1

Da Python zwei Sorten von Strings hat (probiert type("") und type(u"")), tritt beim Testen auf Stringsein übrigens noch eine kleine Komplikation hinzu – man möchte auf die gemeinsame Basisklasse von str und unicode testen. Diese gemeinsame Basisklasse heißt basestring, und das erklärt das oben gegebene Rezept.

Man kann auch von eingebauten Klassen erben. So kann man ein Dictionary, das für unbekannte Schlüssel einfach None zurückgibt, so schreiben:

class DictWithNone(dict):
  def __init__(self, *args):
    dict.__init__(self, args)

  def __getitem__(self, key):
    if self.has_key(key):
      return dict.__getitem__(self, key)
    return None

(was zugegebenermaßen ein paar Dinge tut, die wir noch nicht können und auch nicht ganz die beste Art ist, sowas zu machen)

Übungen zu diesem Abschnitt

Ihr solltet euch wenigstens an den rötlich unterlegten Aufgaben versuchen

(1)

Überzeugt euch anhand des Toy-Beispiels, dass ininstance schlau genug ist, um mit Vererbung zurechtzukommen:

>>> t = BeepingToy("Fred")
>>> isinstance(t, Toy)

usf.

(2)

Seht nach, was type bei euch zurückgibt, wenn ihr ihm len, range oder eine selbst definierte Funktion übergebt. Was gibt sie bei str, dict, list usf., was bei open zurück?

(3)

Schreibt eine Funktion addTo(number, whatever)->float, die “irgendwas” auf eine Zahl addiert. Ist whatever ein integer oder ein float, ist klar, was passieren muss, ist es ein String, würde man wahrscheinlich versuchen, ihn in einen Float zu wandeln. Bei einer Sequenz könnte man z.B. die Länge der Sequenz addieren, bei einer Datei vielleicht die Länge des von read zurückgegebenen Strings. Wie ihr das macht, ist egal, es kommt nur drauf an, dass ihr möglichst viele Typen verarbeiten könnt.

Dateien zu diesem Abschnitt


Markus Demleitner

Copyright Notice