OOP - Python Vererbung
Inhalt
OOP - Python Vererbung¶
Wir wollen hier nun fortschrittliche Konzepte der Vererbung besprechen. Diese Prinzipien sollten verstanden werden, wenn größer Python Projekte umgesetzt werden sollen. Ein gutes Verständnis erleichtert auch das Lesen des Quellcodes anderer Projekte.
Name Mangling¶
Attribute¶
class A:
def __init__(self):
self.x = 'a'
self._x = 'b'
self.__x = 'c'
a = A()
a.__dict__
{'x': 'a', '_x': 'b', '_A__x': 'c'}
class B(A):
def __init__(self):
super().__init__()
self.x = 'override'
self._x = 'override'
self.__x = 'override'
b = B()
b.__dict__
{'x': 'override', '_x': 'override', '_A__x': 'c', '_B__x': 'override'}
b._A__x
'c'
Methoden¶
class A:
def __init__(self):
self.x = 'a'
self._x = 'b'
self.__x = 'c'
def xprint(self):
print(self.x)
def _xprint(self):
print(self._x)
def __xprint(self):
print(self.__x)
a = A()
a.xprint()
a._xprint()
a._A__xprint()
a
b
c
class B(A):
def __init__(self):
super().__init__()
self.x = 'override'
self._x = 'override'
self.__x = 'override'
def xprint(self):
print(self.x)
def _xprint(self):
print(self._x)
def __xprint(self):
print(self.__x)
b = B()
b.xprint()
b._xprint()
b._A__xprint()
b._B__xprint()
override
override
c
override
class A:
def __init__(self):
# going to be overwritten
self.x = 'a'
self._x = 'b'
self.__x__ = 'c'
self.__x___ = 'd'
# name mangiling
self.__x = 'aa'
self.__x_ = 'bb'
self.___x = 'cc'
# going to be overwritten
def xprint(self):
print(self.x)
def _xprint(self):
print(self._x)
def __xprint__(self):
print(self.__x__)
def __xprint___(self):
print(self.x)
# name mangiling
def __xprint(self):
print(self._x)
def __xprint_(self):
print(self.__x_)
def ___xprint(self):
print(self.___x)
a = A()
a.__dict__
{'x': 'a',
'_x': 'b',
'__x__': 'c',
'__x___': 'd',
'_A__x': 'aa',
'_A__x_': 'bb',
'_A___x': 'cc'}
class B(A):
def __init__(self):
super().__init__()
# going to be overwritten
self.x = 'override'
self._x = 'override'
self.__x__ = 'override'
self.__x___ = 'override'
# name mangling
self.__x = 'name mangling'
self.__x_ = 'name mangling'
self.___x = 'name mangling'
# going to be overwritten
def xprint(self):
print(self.x)
def _xprint(self):
print(self._x)
def __xprint__(self):
print(self.__x__)
def __xprint___(self):
print(self.x)
# name mangiling
def __xprint(self):
print(self._x)
def __xprint_(self):
print(self.__x_)
def ___xprint(self):
print(self.___x)
b = B()
b.__dict__
{'x': 'override',
'_x': 'override',
'__x__': 'override',
'__x___': 'override',
'_A__x': 'aa',
'_A__x_': 'bb',
'_A___x': 'cc',
'_B__x': 'name mangling',
'_B__x_': 'name mangling',
'_B___x': 'name mangling'}
b._A__xprint()
override
[ attr for attr in dir ( b ) if "A" in attr ]
['_A__x']
Gezieltes überschreiben¶
Warnung
Das name mangling erschwert das überschreiben von Klassenattributen, kann aber das gezielte überschreiben von diesen nicht verhindern. Deshalb ist es in Python sehr wichtig and Konventionen wie PEP8 oder von großen Projekten zu halten.
class A:
def __init__(self):
# going to be overwritten
self.__x = 'a'
a = A()
a.__dict__
{'_A__x': 'a'}
class B(A):
def __init__(self):
super().__init__()
# going to be overwritten
self._A__x = 'override'
b = B()
b.__dict__
{'_A__x': 'override'}
Warnung
Wir haben hier das name mangling durch gezieltes überschreiben verhindert. Dieses Vorgehen erfüllt jedoch keinen Zweck, zeigt jedoch das Python kein Datenschutzkonzept wie andere Sprachen verfolgt.
Fazit¶
Tipp
Wir haben gesehen das name mangling für Klassenattribute verwendet wird, die nicht von Unterklassen verwendet werden sollen.
Bei __x
, ___x
, __x_
wird name mangling verwendet. Hingegen werden x
, _x
, __x__
, __x___
durch die Unterklassen überschrieben.
Vererbung von __slots__¶
Die Vererbung von __slots__
kommt in verschiedenen Varianten.
Variante 1¶
class A:
__slots__ = 'x'
def __init__(self):
self.x = 1
class B(A):
def __init__(self):
self.y = 2
a = A()
# a.x2 = 'b' # attribute error
b = B()
b.z = 3
b.__slots__
'x'
b.__dict__
{'y': 2, 'z': 3}
Obwohl die Elternklasse eine Slotted Class ist, wird diese Eigenschaft nicht auf die Kindklasse vererbt. Die Kindklasse ist also wieder eine Klasse wo dynamisch Attribute hinzugefügt werden.
Variante 2¶
Damit die Kindklasse auch eine Slotted Class wird muss auch dort das Attribute __slots__
hinzugefügt werden.
class A:
__slots__ = ('x',)
def __init__(self):
self.x = 1
class B(A):
__slots__ = ('y',)
def __init__(self):
self.y = 2
a = A()
a.x
1
# a.z = 3 # attribute error
b = B()
b.y
2
# b.z = 3 # attribute error
b.__slots__
('y',)
Nicht optimal ist hingegen, wenn Attribute die in der Elternklasse schon vorhanden sind in der Kindklasse auch hinzugefügt werden. Das führt zu einer größeren Objektgröße.
class A:
__slots__ = ('x',)
def __init__(self):
self.x = 1
class C(A):
__slots__ = ('x','y')
def __init__(self):
self.y = 2
a = A()
a.x
1
c = C()
c.y
2
c.__slots__
('x', 'y')
from pympler import asizeof
asizeof.asizeof(b)
80
asizeof.asizeof(c)
88
Variante 3¶
Ähnlich wie die Variante 1 können nun in der Kindklasse dynamisch Attribute hinzugefügt werden. So können wieder Klassen optimiert werden.
class A:
__slots__ = ('x')
def __init__(self):
self.x = 1
class C(A):
__slots__ = ('y','__dict__')
def __init__(self):
self.y = 2
c = C()
c.z = 3
c.zz = 4
c.__dict__
{'z': 3, 'zz': 4}
Fazit¶
Das Vererben von __slots__
sollte sudiert werden da gewisse Module der Standardbibliothek davon viel Gebrauch machen.