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.