Private Variablen

Grundlegendes

"Private" Instanzvariablen, auf die nur in einem Objekt zugegriffen werden kann, gibt es in Python nicht. Allerdings gibt es eine Konvention, die von den meisten Python-Programmierern eingehalten wird: ein Name mit einem vorangestellten Unterstrich (z.B. _spam) wird als nicht-öffentlicher Teil einer API behandelt (egal, ob es eine Funktion, eine Methode oder ein Datenelement ist). Es sollte als ein Implementierungs-Detail betrachtet werden und Änderungen vorbehalten sein.

Da es für private Klassenvariablen einen stichhaltigen Use-Case gibt (nämlich um Namenskonflikte von Namen mit Namen zu vermeiden, die in Unterklassen definiert sind), gibt es eine begrenzte Unterstützung für einen Mechanismus, namens Name Mangling, solche Namenskonflikte zu vermeiden. Jeder Name in der Form __spam (d.h. mindestens zwei führende Unterstriche und höchstens ein einziger nachfolgender Unterstrich) wird textuell durch _classname__spam ersetzt, wobei classname der aktuelle Klassenname ist, wobei alle führenden Unterstriche weggelassen werden. Dieser Mechanismus erfolgt ohne Rücksicht auf die syntaktische Position des Namens, solange er innerhalb der Definition einer Klasse auftritt.

Name Mangling ist insbesondere notwendig, damit Unterklassen Methoden überschreiben können ohne dass Methoden Aufrufe innerhalb der Klasse davon berührt werden. Beispielsweise:

In [2]:
class Mapping:
    def __init__(self, iterable):
        self.items_list = []
        self.__update(iterable)

    def update(self, iterable):
        for item in iterable:
            self.items_list.append(item)

    __update = update   # private copy of original update() method

class MappingSubclass(Mapping):

    def update(self, keys, values):
        # provides new signature for update()
        # but does not break __init__()
        for item in zip(keys, values):
            self.items_list.append(item)
In [3]:
l = [1,2,3,4]
iMapping = Mapping(l)
m = [5,6,7,8]
iMapping.update(m)
print(iMapping.items_list)
[1, 2, 3, 4, 5, 6, 7, 8]
In [4]:
l = [1,2,3,4]
iMappingSub = MappingSubclass(l)
m = [5,6,7,8]
iMappingSub.update(['eins','zwei','drei','vier'],m)
print(iMappingSub.items_list)
[1, 2, 3, 4, ('eins', 5), ('zwei', 6), ('drei', 7), ('vier', 8)]

Sieht man sich die Version ohne Name Mangling an, so erkennt man die Problematik beim Aufruf der Instanziierung der Unterklasse MappingSubclass:

In [5]:
class Mapping:
    def __init__(self, iterable):
        self.items_list = []
        self.update(iterable)

    def update(self, iterable):
        for item in iterable:
            self.items_list.append(item)

class MappingSubclass(Mapping):

    def update(self, keys, values):
        # provides new signature for update()
        # but does not break __init__()
        for item in zip(keys, values):
            self.items_list.append(item)
In [6]:
l = [1,2,3,4]
iMapping = Mapping(l)
m = [5,6,7,8]
iMapping.update(m)
print(iMapping.items_list)
[1, 2, 3, 4, 5, 6, 7, 8]
In [7]:
l = [1,2,3,4]
iMappingSub = MappingSubclass(l)
m = [5,6,7,8]
iMappingSub.update(['eins','zwei','drei','vier'],m)
print(iMappingSub.items_list)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-7-fd96a2ff309d> in <module>()
      1 l = [1,2,3,4]
----> 2 iMappingSub = MappingSubclass(l)
      3 m = [5,6,7,8]
      4 iMappingSub.update(['eins','zwei','drei','vier'],m)
      5 print(iMappingSub.items_list)

<ipython-input-5-f9d246fe467c> in __init__(self, iterable)
      2     def __init__(self, iterable):
      3         self.items_list = []
----> 4         self.update(iterable)
      5 
      6     def update(self, iterable):

TypeError: update() missing 1 required positional argument: 'values'
In [8]:
%%Mooc StringAssessment
Out[8]:

Name Mangling gilt auch für Attribute

In dem folgenden Code wird die Klasse Ort mit der Berechnung der Distanz auf einer Kugel durchgeführt. Dazu wird der Erdradius benötigt. Dieser wird als private Variable eingesetzt. Diese private Variable kann trotzdem in einer Instanz (z.B. hier in mm) verändert werden. Wie verändert sich dann das Ergebnis der Distanzberechnung aufgrund des veränderten Erdradius? Berechnen sie die Differenz zwischen der Distanz vor der Veränderung des Erdradius und der Berechnung der Distanz nach der Veränderung!


class Ort():

    __erdradius = 6378.137 # Äquatorradius in km (WGS84 Referenzellipsoid)
    # siehe https://de.wikipedia.org/wiki/Orthodrome
    
    def __init__(self,nord,ost,beschreibung=""):
        self.Nord = nord
        self.Ost = ost
        self.Beschreibung = beschreibung
        
    def distanz(self,andererOrt):
        from math import sin, cos, acos, radians
        lat1 = radians(self.Nord)
        lat2 = radians(andererOrt.Nord)
        lon1 = radians(self.Ost)
        lon2 = radians(andererOrt.Ost)
        d = self.__erdradius * acos(sin(lat1) * sin(lat2) + 
                                    cos(lat1) * cos(lat2) * cos(lon2 - lon1))
        return d

mm = Ort(47.98, 10.17,"Memmingen")
nö = Ort(48.84,10.5,"Nördlingen")

mm_nö_1 = mm.distanz(nö)

mm.__erdradius = 5000
mm_nö_2 = mm.distanz(nö)

print("__erdradius={}, Differenz mm_nö_1-mm_nö_2={}".format(mm.__erdradius,mm_nö_1-mm_nö_2))

Geben sie das Ergebnis der Print-Funktion an.



Zusätzliche Informationen

Name Mangling ist entwickelt worden, um Störfälle zu vermeiden; Dies bedeutet, dass es immer noch möglich ist, auf eine als privat geltende Variable zuzugreifen oder diese zu ändern. Dies kann sogar unter besonderen Umständen sinnvoll sein, beispielsweise im Debugger.

Diverses

Manchmal ist es sinnvoll, einen Datentyp verwenden zu können, der entsprechende Datenelemente zusammenfasst (vergleichbar dem Pascal "record" oder C "struct" Konstrukten). Dies kann durch eine leere Klassendefinition erreicht werden, deren Instanzen dann jeweils einem entsprechenden Record vergleichbar ist:

In [9]:
class Mitarbeiter:
    pass

michael = Mitarbeiter()

michael.Name = 'Michael Mustermann'
michael.Abteilung = 'Rechenzentrum'
michael.Gehalt = 2000

Python-Code, der einen bestimmten abstrakten Datentyp erwartet, kann auch eine Klasse übergeben werden, die die Methoden dieses Datentyps emuliert. Wenn beispielsweise eine Funktion gegeben ist, die Daten aus einer Datei einliest und verwendet, können Sie eine zusätzliche Klasse mit den Methoden read() und readline() definieren, die stattdessen die Daten von einem String-Puffer erhalten und als Argument übergeben.

Instanzmethodenobjekte haben auch Attribute: m.__ self__ ist das Instanzobjekt mit der Methode m() und m.__func__ ist das der Methode entsprechende Funktionsobjekt.

In [10]:
class B:
    def methode(self, arg):
        print("B.methode: arg={}".format(arg))

arg = "Argument arg"
instanzB = B()
instanzBmethode = instanzB.methode
In [11]:
print(instanzBmethode.__self__ == instanzB)
True
In [12]:
print(instanzBmethode.__func__ == B.methode)
True
In [13]:
instanzBmethode.__func__(instanzB, arg)
B.methode: arg=Argument arg

Weitere Literatur

In [14]:
%%Mooc WebReference

Private Variables

https://docs.python.org/3/tutorial/classes.html#private-variables

Hinweis: Private Variablen

In [15]:
%%Mooc WebReference

Odds and Ends

https://docs.python.org/3/tutorial/classes.html#odds-and-ends

Hinweis: Diverses zu Klassen