NB_02_Vertiefung¶
(c) 2024 Technische Hochschule Augsburg - Fakultät für Informatik - Prof.Dr.Nik Klever - Impressum
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:
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)
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]
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:
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)
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]
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'
%%Mooc StringAssessment
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:
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.
class B:
def methode(self, arg):
print("B.methode: arg={}".format(arg))
arg = "Argument arg"
instanzB = B()
instanzBmethode = instanzB.methode
print(instanzBmethode.__self__ == instanzB)
True
print(instanzBmethode.__func__ == B.methode)
True
instanzBmethode.__func__(instanzB, arg)
B.methode: arg=Argument arg
Weitere Literatur¶
%%Mooc WebReference
Private Variables
https://docs.python.org/3/tutorial/classes.html#private-variables
Hinweis: Private Variablen
%%Mooc WebReference
Odds and Ends
https://docs.python.org/3/tutorial/classes.html#odds-and-ends
Hinweis: Diverses zu Klassen