\section {Attribute und Methoden}
\label {att-meth}

  \subsection {User-provided vs. Python-provided Attributes}

    Wie schon in Abschnitt~\ref {objdef} vorweggenommen, haben alle
    Objekte Attribute, die durch den Punktoperator referenziert werden
    können. Man unterscheidet zwischen benutzerdefinierten Attributen
    (\emph {user-provided attributes}) und denjenigen, die der
    Pythoninterpreter automatisch für jedes Objekt bereitstellt (\emph
    {python-provided attributes}).\footnote {Eine vollständige Liste
    dieser Attribute findet sich in \cite[Kapitel~2.3.11]{pylib}.} Unter
    letzteren ist auch ein Attribut namens \lstinline $__dict__$.  Wie
    der Name suggeriert, ist es ein Dictionary.\footnote {Bei
    Klassenobjekten ist \lstinline $__dict__$ vom Typ \lstinline
    $dictproxy$, läßt sich aber leicht in ein \lstinline $dict$
    umwandeln, wie im nächsten Beispiel zu sehen ist.}  Darin werden
    alle benutzerdefinierten Attribute des entsprechenden Objekts
    gespeichert. Der Schlüssel ist immer der Name des Attributs und der
    zugeordnete Wert ein beliebiges Objekt.

    Der Rest dieses Abschnitts behandelt ausschließlich
    benutzerdefinierte Attribute.

  \subsection {Einfacher Attributzugriff}

    Der lesende und schreibende Zugriff auf Objektattribute mit Hilfe
    des Punktoperators ist nur eine spezielle Syntax für den Aufruf der
    Methoden \lstinline $__getattribute_$ und \lstinline
    $__setattr__$,\footnote {Der Grund für die Asymmetrie in der
    Benennung der beiden Methoden wird in \citep[Kapitel~3.3.2 ff.]
    {pyref} beschrieben.} die alle Objekte von \lstinline $object$ bzw.
    \lstinline $type$ erben. Betrachten wir zunächst das Verhalten
    dieser Methoden. 

    \subsubsection{Setzen von Attributen}

      \lstinputlisting[float=tb,caption=Klassen- und Instanzattribute,
        label=lst:classattrs1,name=Attrs]{lst/classattrs1.py}

      Alle Zuweisungen und Funktionsdefinitionen auf der obersten Ebene
      innerhalb einer Klassendefinition erzeugen neue Attribute \emph
      {der Klasse}. Listing~\vref {lst:classattrs1} zeigt ein Beispiel
      und demonstriert in Zeile~\ref {classattrs1:dict}, dass
      \lstinline $__init__$, \lstinline $f$ und \lstinline $classattr$
      tatsächlich im \lstinline $__dict__$ der Klasse \lstinline $C$
      gespeichert werden.\footnote {Aus Gründen der Übersichtlichkeit
      wurden einige Attribute weggelassen, die Klassen normalerweise
      ebenfalls haben.} Es sei erwähnt, dass Klassen die Methoden
      \lstinline $__setattr__$ und \lstinline $__getattribute__$ von
      \lstinline $type$ erben, nicht von \lstinline $object$. Aus diesem
      Grund weicht ihr Verhalten von normalen Instanzen ab.

      Anders erzeugt man Attribute von Instanzen, deren Klasse nicht von
      \lstinline $type$ ableitet. Betrachten wir Listing~\ref
      {lst:classattrs1} erneut. Die beiden Parameter \lstinline $"foo"$
      und \lstinline $"bar"$, die dem Konstruktor in Zeile~\ref
      {classattrs1:constr} übergeben wurden, werden in der Methode
      \lstinline $__init__$ an die Parameter mit den Namen \lstinline
      $x$ und \lstinline $y$ gebunden. Der erste Parameter \lstinline
      $self$ ist wie immer an die Instanz gebunden, die die Nachricht
      \lstinline $__init__$ empfangen hat. Die Mehrfachzuweisung in
      Zeile~\ref {classattrs1:zuweisg1} erzeugt also zwei neue
      benutzerdefinierte Attribute \lstinline $x$ und \lstinline $y$,
      die man -- wie erwartet -- in \lstinline $obj.__dict__$ findet
      (Zeile~\ref {classattrs1:dict1}). Es gibt also keine andere
      Möglichkeit, als Objektattribute erst \emph{nach} der
      Instanzierung des Objekts zu erzeugen. Möchte man sicherstellen,
      dass die Instanz jeder Klasse initial bestimmte Attribute hat,
      erzeugt man sie gewöhnlich in der Methode \lstinline $__init__$.

      Es dürfte nun nicht mehr überraschen, dass jedem Objekt, das nicht
      immutable ist, zur Laufzeit beliebige Attribute zugewiesen
      (siehe Zeile~\ref {classattrs1:new}) und natürlich auch gelöscht
      werden können, siehe Zeile~\ref {classattrs1:del}. Das Löschen
      von Attributen mit \lstinline $del$ ist übrigens auch durch eine
      Objektmethode realisiert und muss sich dementsprechend nicht
      genauso verhalten wie hier demonstriert.

    \subsubsection {Lesen von Attributen}

      \lstinputlisting[float=tb,caption=Attributzugriff,
      label=lst:classattrs2,firstnumber=22,name=Attrs]{lst/classattrs2.py}

      Beim lesenden Zugriff auf ein Objektattribut hört die Suche nach
      einem entsprechend benannten Attribut nicht im \lstinline
      $__dict__$ des Objekts auf. Listing~\vref {lst:classattrs2} führt
      das vorige Beispiel fort. Wie zu sehen ist, sind Attribute einer
      Klasse auch über deren Instanzen referenzierbar. Wird das
      angeforderte Attribut nicht im \lstinline $__dict__$ des Objekts
      gefunden, wird in seiner Klasse danach gesucht. Bleibt dies
      ebenfalls erfolglos, werden rekursiv deren Oberklassen
      durchsucht.\footnote {Die genaue Reihenfolge beim Durchsuchen von
      Oberklassen bei Mehrfachvererbung ist sehr detailliert in \citep
      {mro} beschrieben.}
      
      Dieser Vorgang findet allerdings nur beim lesenden Zugriff statt.
      Beim Setzen eines Attributs wird immer direkt in das \lstinline
      $__dict__$ des angesprochenen Objekts geschrieben, wie in
      Zeile~\ref {classattrs2:write} zu sehen ist. Danach überdeckt das
      Attribut der Instanz das gleichnamige Attribut seiner Klasse.
      So ungewohnt dieser Effekt zuerst erscheinen mag, läßt er sich
      doch geschickt nutzen. Zum Beispiel können in einer Klasse
      Attribute mit Defaultwerten bestückt werden, die von allen ihren
      Instanzen geteilt werden, bis der Wert dieser Attribute in
      einzelnen Instanzen neu gesetzt wurde. Der Unterschied zum Setzen
      der Attribute in der \lstinline $__init__$-Methode ist, dass das
      Klassenattribut nur \emph {einmal} erzeugt wird. Alle Instanzen
      teilen sich in diesem Fall dasselbe Objekt.

    \subsubsection {Funktionen und Methoden}
    \label {self}

      Das oben beschriebene Verhalten beim Zugriff auf Klassenattribute
      über eine Instanz der Klasse hilft auch beim Verständnis der
      Entstehung von \emph {Methoden}. Denn bisher haben wir nur
      gesehen, wie \emph {Funktionen} innerhalb einer Klasse definiert
      werden können (siehe Listing~\ref{lst:classattrs1}, Zeile~\ref
      {classattrs1:dict} auf Seite \pageref {classattrs1:dict}).

      Funktionen und Methoden sind in Python Instanzen unterschiedlicher
      Klassen. Eine spezielle Syntax gibt es nur zur Definition von
      Funktionen (Schlüsselwort \lstinline $def$). Methoden werden vom
      Interpreter automatisch für alle Funktionen innerhalb einer Klasse
      erzeugt. Sie agieren als Wrapper für Funktionen. Alle Methoden
      haben drei in diesem Zusammenhang interessante Attribute:\footnote
      {Siehe \citep [Kapitel~3.2, User-defined Methods]{pyref}}
      
      \begin{itemize}
        \item \lstinline $im_func$ ist das Funktionsobjekt, das von der
          Methode gekapselt wird,
        \item \lstinline $im_class$ ist die Klasse, in der die
          gekapselte Funktion definiert wurde,
        \item \lstinline $im_self$ ist \lstinline $None$ oder die
          Instanz, auf der die Methode aufgerufen wird.
      \end{itemize}
      
      \lstinputlisting[float=tb,caption=Funktionen und Methoden,
      label=lst:methods,firstnumber=31]{lst/methods.py}
      
      Listing~\vref {lst:methods} veranschaulicht diese Zusammenhänge.
      Zu beachten ist der Unterschied zwischen \emph {bound} und \emph
      {unbound methods}. Die Methoden \lstinline $C.f$ und \lstinline
      $obj.f$ sind verschiedene Objekte. Bei ungebundenen Methoden ist
      das Attribut \lstinline $im_self$ gleich \lstinline $None$.
      Gebundene Methoden "`kennen"' dagegen ihren Aufrufer. Diese
      Unterscheidung ist wichtig, weil \lstinline $im_self$ genau das
      Attribut ist, das die Methode beim Aufruf ihrer gekapselten
      Funktion \lstinline $im_func$ als ersten Parameter (gewöhnlich mit
      dem Namen \lstinline $self$) übergibt. Dabei findet sogar eine
      Typüberprüfung statt. Ist \lstinline $im_self$ keine Instanz von
      \lstinline $im_class$, wird ein \lstinline $TypeError$ erzeugt.
      Ungebundene Methoden können trotzdem erfolgreich aufgerufen
      werden: der Aufruf \lstinline $C.f(obj)$ ist in unserem Beispiel
      äquivalent zu \lstinline $obj.f()$.\footnote {Vgl.
      \cite[Kapitel~9.3.4]{pytut}} Diese Notation wird hauptsächlich
      dann benutzt, wenn innerhalb einer Klassendefinition überladene
      Methoden einer Oberklasse aufgerufen werden sollen.

  \subsection{Deskriptoren}

    Es gibt viele Situationen, in denen man Einfluss auf lesenden und
    schreibenden Zugriff auf Objektattribute nehmen möchte. In Java
    würde man hierfür typischerweise Getter- und Setter-Methoden
    implementieren, die private Attribute manipulieren. In Ruby wird
    syntaktisch erst gar kein Unterschied zwischen Attributen und
    Methoden gemacht. Für den Aufrufer ist damit gar nicht klar, ob er
    das Ergebnis einer Berechnung oder ein (nicht im objektorientierten
    Sinn) "`statisches"' Objekt zurückgeliefert bekommt.

    Der offensichtlichste Weg, in Python Einfluss auf Attributzugriffe
    zu nehmen, wäre die Methoden \lstinline $__getattribute__$ und
    \lstinline $__setattr__$ zu überladen, die den lesenden
    beziehungsweise schreibenden Attributzugriff implementieren. Das
    zieht aber einige Nachteile nach sich. Zum Einen werden die
    entsprechenden Methoden für \emph {alle} Attribute eines Objekts
    aufgerufen. Das kostet Performance und macht den Code leicht
    unübersichtlich. Zum Anderen ist -- gerade im Umgang mit \lstinline
    $__getattribute__$ -- besondere Vorsicht von Nöten, um endlose
    Rekursion zu vermeiden. Mit Python~2.2 wurde eine neue Lösung für
    dieses Problem eingeführt: Deskriptoren.
  
    Rein technisch betrachtet sind Deskriptoren alle Objekte, die das
    sogenannte Deskriptorprotokoll implementieren, das aus den Methoden
    \lstinline $__get__$, \lstinline $__set__$ und \lstinline
    $__delete__$ besteht. Die letzten beiden Methoden sind
    optional.\footnote {Vgl. \cite[Creating Descriptors]{att-meth}} Wird
    beim Attributzugriff ein entsprechend benannter Deskriptor im
    \lstinline $__dict__$ der \emph {Klasse} des Objekts gefunden, wird
    nicht der Deskriptor selbst zurückgegeben, sondern dessen der
    Zugriffsart entsprechende Methode aufgerufen. Das \lstinline
    $__dict__$ der Instanz wird hierbei übergangen! Beim lesenden
    Zugriff wird \lstinline $__get__$ aufgerufen, beim Schreiben
    \lstinline $__set__$ und beim Löschen des Attributs \lstinline
    $__delete__$.  Die genaue Signatur der genannten Methoden ist
    Listing~\ref {lst:minimal-descr} zu sehen.\footnote {Ebd.} Der
    Parameter \lstinline $obj$ ist das jeweilige Objekt, dessen Attribut
    abgefragt wird und \lstinline $typ$ dessen Typ.

    \lstinputlisting[float=tb,caption=Das Deskriptorprotokoll,
    label=lst:minimal-descr,] {lst/minimal-descr.py}
      
    \subsubsection {Data- und Non-Data-Descriptors}
      
      Unterschieden werden zwei Arten von Deskriptoren: \emph {Data
      Descriptors} und \emph {Non-Data Descriptors}. Data Descriptors
      werden all diejenigen Deskriptoren genannt, die sowohl die Methode
      \lstinline $__get__$ als auch \lstinline $__set__$ implementieren.
      Deskriptoren, die nur \lstinline $__get__$ implementieren, heißen
      Non-Data Descriptors. Die Methode \lstinline$__delete__$ spielt
      für diese Unterscheidung keine Rolle.

      Das Besondere an Non-Data Descriptors ist, dass sie von regulären
      Objektattributen überdeckt werden können. Betrachten wir
      Listing~\vref {lst:non-data-descriptors} als Beispiel.\footnote
      {Beispiel angelehnt an \citep[Beispiel 1.5]{att-meth}.} Die Klasse
      \lstinline $Descr$ implementiert ausschließlich die Methode
      \lstinline $__get__$. Damit sind dessen Instanzen Non-Data
      Descriptors. Die Klasse \lstinline $C$ enthält ein Attribut von
      eben diesem Typ, wie in Zeile~\ref {non-dd:type-d} zu sehen ist.
      Und wie erwartet, können wir in Zeile~\ref {non-dd:get} erkennen,
      dass dieses Klassenattribut nicht nur über die Instanz \lstinline
      $obj$ erreichbar ist, sondern auch das Ergebnis der Methode
      \lstinline $__get__$ statt des Deskriptors zurückgegeben wird.
      Soweit also keine Überraschungen.

      \lstinputlisting[float=tb,caption=Non-Data Descriptors,
      label=lst:non-data-descriptors,name=Descriptors]
      {lst/non-data-descriptors.py}

      Setzen wir aber das Attribut \lstinline $d$ von \lstinline $obj$
      neu wie in Zeile~\ref {non-dd:set}, überdeckt anschließend der
      neue Wert im \lstinline $__dict__$ von \lstinline $obj$ den
      Deskriptor in \lstinline $C$. Dieser Vorgang ist einfach
      rückgängig zu machen, indem das Attribut \lstinline $d$ wieder aus
      \lstinline $obj$ gelöscht wird (Zeile~\ref {non-dd:del}).

    \subsubsection {Built-Ins}
    \label {properties}

      Seit Deskriptoren verfügbar sind, macht Python intern exzessiven
      Gebrauch davon. Prominentestes Beispiel hierfür sind Funktionen.
      Funktionen sind Non-Data Descriptors. Zum Einen macht dies
      möglich, dass der Zugriff auf eine Funktion eine bound
      beziehungsweise eine unbound method zurückliefert, je nachdem, ob
      der Zugriff auf die Funktion über eine Instanz oder deren Klasse
      geschieht. Zum Anderen können Methoden auch für einzelne Instanzen
      überschrieben werden, denn das \lstinline $__dict__$ der Instanz
      hat Vorrang über Non-Data Descriptors in der Klasse.

      Weitere eingebaute Deskriptoren sind \lstinline $property$,
      \lstinline $classmethod$ und \lstinline $staticmethod$. Properties
      wurden bereits in Abschnitt~\ref {properties-mentioned}
      angesprochen und in Listing~\ref {lst:metaprog1} benutzt. Ihre
      Funktionsweise ist äußerst simpel: der Konstruktor nimmt bis zu
      drei Funktionen für das Lesen, Setzen und Löschen eines Attributs
      entgegen, die im entsprechenden Fall aufgerufen werden. Im
      Ergebnis verhalten sich Properties also wie Attribute mit Getter
      und Setter in anderen Programmiersprachen, nur dass deren
      Benutzung völlig transparent ist. Ein reguläres Attribut kann auch
      im Nachhinein durch ein Objekt vom Typ \lstinline $property$
      ersetzt werden, ohne dass sich die Schnittstelle ändert.

      Die Klassen \lstinline $classmethod$ und \lstinline $staticmethod$
      erzeugen alternative Wrapper für Funktionen innerhalb von Klassen.
      Ihre Instanzen verhalten sich ähnlich wie Methoden, nur dass im
      Fall von \lstinline $classmethod$ der erste übergebene Parameter
      nicht die Instanz des aufgerufenen Objekts ist, sondern dessen
      Klasse, und im Fall von \lstinline $staticmethod$ überhaupt kein
      künstlicher erster Parameter eingefügt wird. Objekte von diesem
      Typ verhalten sich also wie normale Funktionen, leben aber im
      Namespace einer Klasse.

    \subsubsection{Decoratorsyntax}

      \lstinputlisting[float=tb,caption=Die Decoratorsyntax,
      label=lst:decorators,name=Decorators] {lst/decorators.py}

      Seit Python~2.4 existiert eine spezielle Syntax für die Benutzung
      von Wrappern für Funktionen innerhalb einer Klasse.\footnote
      {Erstmals wurde diese Syntax in \citep{pep318} als das Ergebnis
      einer langen Diskussion beschrieben.} Sie wird meist im
      Zusammenhang mit den eben erwähnten Deskriptoren benutzt, ist aber
      für beliebige Transformationen von Funktionen anwendbar.
      Listing~\vref {lst:decorators} zeigt die ursprüngliche und die
      neue Schreibweise, die im Ergebnis äquivalent sind.

      Die erste Klassendefinition benutzt die herkömmliche Schreibweise,
      die schon in Python~2.2 erlaubt war. Es werden zwei Funktionen
      \lstinline $f$ und \lstinline $g$ definiert, die anschließend mit
      dem Rückgabewert der Wrapper überschrieben werden. Problematisch
      an dieser Schreibweise ist, dass recht leicht zu übersehen ist,
      dass die bereits definierten Funktionen später wieder
      überschrieben werden. Insbesondere bei längeren Funktionen ist der
      Zusammenhang der beiden Ausdrücke nicht immer leicht zu erkennen.
      Die neue Decoratorsyntax verschiebt die Anweisung zur
      Transformation direkt vor den Rumpf der betroffenen Funktion und
      behebt so diesen Mangel. Direkt notwendig ist sie aber nicht.

      Die neue Syntax ist dennoch sehr nützlich, denn sie betont eine
      äußerst mächtige Technik. Das dekorieren von Funktionen kann für
      viele verschiedene Zwecke eingesetzt werden, zum Beispiel für das
      Prüfen von Vor- und Nachbedingungen einer Funktion.

  \subsection{Zusammenfassung Attributzugriff}

    Um Übersicht in die lange Geschichte des Attributzugriffs in
    Verbindung mit Deskriptoren zu bringen, folgt hier noch einmal eine
    komprimierte, vollständige Auflistung der Schritte, die Python beim
    Zugriff auf ein Objektattribut ausführt:\footnote {Nach
    \citep[Attribute Search Summary]{att-meth}.}

    \subsubsection{Lesender Zugriff}

      \begin{enumerate}

        \item Wenn das gesuchte Attribut python-provided ist, gib es
          zurück.

        \item Durchsuche die Klasse des Objekts und deren Oberklassen
          nach einem Data Descriptor. Wenn einer gefunden wird, rufe
          seine \lstinline $__get__$-Methode auf und gib dessen Ergebnis
          zurück.

        \item Suche im Objekt nach dem Attribut und gib es zurück. Wenn
          das Objekt eine Instanz von \lstinline $type$ ist, werden auch
          seine Oberklassen durchsucht. Ist dies der Fall, und das
          gefundene Attribut ist ein Deskriptor, wird das Ergebnis
          seiner \lstinline $__get__$-Methode zurückgegeben.

        \item Suche in der Klasse des Objekts und deren Oberklassen nach
          dem Attribut und gib es zurück. Ist es ein Deskriptor, gib das
          Ergebnis seiner \lstinline $__get__$-Methode zurück.

        \item Wurde das Attribut nicht gefunden, erzeuge einen
          \lstinline $AttributeError$.

      \end{enumerate}


    \subsubsection{Schreibender Zugriff}

      \begin{enumerate}

        \item Durchsuche die Klasse des Objekts und deren Oberklassen nach
          einem Data Descriptor. Wird einer gefunden, rufe seine
          \lstinline $__set__$-Methode mit den entsprechenden Parametern
          auf.

        \item Wurde kein Data Descriptor gefunden, erzeuge ein neues
          Attribut im Objekt.

      \end{enumerate}

      Das Setzen von Attributen, die vom Interpreter bereitgestellt
      werden, verhält sich je nach Attribut unterschiedlich. Einige sind
      manipulierbar, andere nicht. Das Löschen von Attributen verhält
      sich ähnlich, wie das Schreiben.

      Es muss erwähnt werden, dass sich die komplexe Prozedur des
      lesenden Attributzugriffs negativ auf die Performance von
      Pythonprogrammen auswirken kann. Aus diesem Grund und zugunsten
      besserer Lesbarkeit findet man in Pythoncode relativ selten tief
      geschachtelte Objekt- und Vererbungshierarchien. Gelegentlich wird
      auch ein sehr häufig gebrauchtes Attribut an einen lokalen Namen
      gebunden und weiterverwendet, anstatt bei jedem Zugriff die
      komplette Zuordnungskette zu durchwandern. Aufgrund der
      Referenzsemantik ist das problemlos möglich.

