Exkurs - Datenkapselung in Python

Öffentliche / private Attribute

Ein Attribut wird in Python zu einem privaten Attribut, wenn der Attributname mit zwei Unterstrichen beginnt und nicht mit Unterstrichen endet. Beginnt der Attributname nicht mit einem Unterstrich, so ist das Attribut öffentlich.

Zur Verdeutlichung betrachten wir die folgende Implementierung der Klasse Ampel:

class Ampel(object):
    def __init__(self):
        self.__zustand = 'rot'
		
    def schalten(self):
        if self.__zustand == 'rot':
            self.__zustand = 'rotgelb'
        elif self.__zustand == 'rotgelb':
            self.__zustand = 'gruen'
        elif self.__zustand == 'gruen':
            self.__zustand = 'gelb'
        elif self.__zustand == 'gelb':
            self.__zustand = 'rot'

    def getLampen(self):
        if self.__zustand == 'rot':
            lampen = (True, False, False)
        elif self.__zustand == 'rotgelb':
            lampen = (True, True, False)
        elif self.__zustand == 'gruen':
            lampen = (False, False, True)
        elif self.__zustand == 'gelb':
            lampen = (False, True, False)
        return lampen

    def getZustand(self):
        return self.__zustand

    def setZustand(self, z):
        self.__zustand = z

Das Attribut __zustand beginnt mit zwei Unterstrichen, ist also ein privates Attribut.

Zugriffsmethoden

Zugriffsmethoden benötigt man, um auf private Attribute lesend und schreibend zugreifen zu können. Das Programm oben zeigt, wie man solche Methoden implementiert.

Solche Zugriffsmethoden benutzt man, um z. B. privaten Attribut Werte zuzuweisen und um diese Werte anschließend auszulesen.

>>> a = Ampel()
>>> a.schalten()
>>> a.getZustand()
'rotgelb'
>>> a.setZustand('rot')
>>> a.getZustand()
'rot'

Achtung: Python-Besonderheiten

Python verhält sich bei privaten Attributen (leider) nicht so restriktiv, wie das eigentlich sein sollte. Der folgende Python-Dialog zeigt einige Besonderheiten von Python.

>>> a = Ampel()
>>> a.__zustand
Traceback (most recent call last):
  File ...
    a.__zustand
AttributeError: 'Ampel' object has no attribute '__zustand'
>>> a.__dict__
{'_Ampel__zustand': 'rot'}
>>> a._Ampel__zustand
'rot'

Wie erwartet kann man auf das private Attribut __zustand des neu erzeugten Objekts a nicht zugreifen. Python meldet als Fehler, dass es kein Attribut __zustand gibt. Der Aufruf a.__dict__ verrät, woran das liegt. Ein Aufruf wie a.__dict__ listet sämtliche Attribute mit den zugehörigen Attributwerten des betreffenden Objekts auf. Interessant ist hier, dass sich das private Attribut __zustand hinter einem anderen Namen versteckt. Wenn man weiß, wie der neue Name - hier _Ampel__zustand - gebildet wird, dann kann man auf das betreffende Attribut zugreifen. Also: Private Attribute werden in Python mit anderen Namen versehen, so dass kein direkter Zugriff möglich ist. Kennt man den Namen, hinter dem sich ein privates Attribut verbirgt, so kann man durchaus auf dieses Attribut zugreifen. Python liefert also keinen echten Zugriffsschutz.

Die völlig unrestiktive Art von Python kann zu unerwartetem Verhalten von Objekten und sehr schwer zu findenden Fehlern führen. Der folgende Dialog zeigt ein solches Beispiel.

>>> a = Ampel()
>>> a.__zustand
Traceback (most recent call last):
  File ...
    a.__zustand
AttributeError: 'Ampel' object has no attribute '__zustand'
>>> a.__dict__
{'_Ampel__zustand': 'rot'}
>>> a._Ampel__zustand
'rot'
>>> a.__zustand = 'gruen'
>>> a.__zustand
'gruen'
>>> a.__dict__
{'_Ampel__zustand': 'rot', '__zustand': 'gruen'}

Ein erster Zugriff auf das private Attribut __zustand scheitert. Dann aber ist es - entgegen aller Zugriffslogik - scheinbar möglich, dem privaten Attribut __zustand einen Wert zuzuweisen. Der Aufruf a.__dict__ erklärt erst, was hier passiert ist. Neben dem privaten Attribut __zustand, das sich hinter dem neuen Namen _Ampel__zustand versteckt, gibt es noch öffentliches Attribut __zustand, auf das man direkt zugreifen kann.

Dieses unerwartete Verhalten liegt letztlich daran, dass Python es erlaubt, Objekten dynamisch neue Attribute hinzuzufügen. Dieses Verhalten lässt sich wie folgt unterbinden.

class Ampel(object):

    __slots__ = ('__zustand')

    def __init__(self):
        self.__zustand = 'rot'

    # ... wie bisher ...

Mit dem Attribut __slots__ wird festgelegt, welche Attribute ein Objekt der betreffenden Klasse haben darf. Im vorliegenden Fall soll ein Ampel-Objekt nur das private Attribute __zustand haben. Python meldet einen Fehler, wenn man versucht, ein weiteres Attribut zu erzeugen. Mit dieser Festlegung ist jetzt das oben gezeigte Verhalten nicht mehr möglich.

>>> a.__zustand
Traceback (most recent call last):
  File ...
    a.__zustand
AttributeError: 'Ampel' object has no attribute '__zustand'
>>> a.__zustand = 'gruen'
Traceback (most recent call last):
  File ...
    a.__zustand = 'gruen'
AttributeError: 'Ampel' object has no attribute '__zustand'

Vereinbarung - Umgang mit privaten Attributen und Methoden

Das Beispiel zeigt, dass man in Python am besten klare Regeln befolgt, um nicht zu solch undurchschaubaren Objektsituationen zu gelangen.

Wir werden im Folgenden bei der Implementierung von Klassen in Python keine Attribute und Methoden als privat deklarieren. Alle Attribute und Methoden sind daher direkt zugänglich. Allerdings werden wir von dem direkten Zugriff in der Regel keinen Gebrauch machen. Nur in begründeten Sonderfällen (wie z.B. zum schnellen Testen) werden wir von dieser Vereinbarung abweichen.

X

Fehler melden

X

Suche