Python-Klassen-Initialisierung: Bemerkenswertes Verhalten
Während der Entwicklung von »BlackRed« ist mir ein Python-Verhalten aufgefallen, welches ich so nicht auf dem Schirm hatte. Man lernt eben nie aus.
Man stelle ich vor, man hätte diese Konstellation vor sich:
class A(object):
class ASub:
A_SUB_VALUE = 1
def __init__(self, a=ASub.A_SUB_VALUE):
self.__a = a
@property
def a(self):
return self.__a
Der Konstruktor __init__
der Klasse A
hat einen Parameter, der mit dem Wert A_SUB_VALUE
aus der Unterklasse ASub
, also initial 1
vorbelegt ist. Erzeugt man nun eine neue Instanz von A
und fragt die Property a
ab, muss also 1
herauskommen. Das passiert auch:
>>> A().a
1
Nun verändert man den Wert A_SUB_VALUE
auf 2
und erzeugt erneut eine Instanz von A
. Welchen Wert hat nun die Property a
? Ich muss zugegeben, zuerst dachte ich (auch), dass es 2
sein müsste. Tatsächlich ist es aber nach wie vor 1
:
>>> A.ASub.A_SUB_VALUE = 2
>>> A().a
1
Ist das nun ein besonderes Verhalten bei verschachtelten Klassen? Um das zu probieren, wird folgende Konstruktion verwendet:
class AValue(object):
A_VALUE = 1
class A(object):
def __init__(self, a=AValue.A_VALUE):
self.__a = a
@property
def a(self):
return self.__a
Damit wird der Test von eben wiederholt, zunächst ohne Änderung von A_VALUE
, dann nochmal mit:
>>> A().a
1
>>> AValue.A_VALUE = 2
>>> A().a
1
Wie würde es also aussehen, wenn man statt dem Wert als Vorbelegung das Ergebnis eines Methodenaufrufs verwendet? Zum Test gibt es diesen Code:
class B(object):
class BSub:
B_SUB_VALUE = 1
@classmethod
def get_b(cls):
return cls.B_SUB_VALUE
def __init__(self, b=BSub.get_b()):
self.__b = b
@property
def b(self):
return self.__b
... und wieder den bekannten Test:
>>> B().b
1
>>> B.BSub.B_SUB_VALUE = 2
>>> B().b
1
Auch hier hat die Vorbelegung im Konstruktor von B
noch immer den ursprünglichen Wert 1
.
Was aber nun, wenn man die Vorbelegung nun aber änderbar braucht bzw. haben möchte? Mir ist dazu nur diese Konstruktion eingefallen:
class C(object):
class CSub:
C_SUB_VALUE = 1
def __init__(self, c=None):
self.__c = c
if c is None:
self.__c = C.CSub.C_SUB_VALUE
@property
def c(self):
return self.__c
Testet man diese Implementierung, erhält man das gewünschte Ergebnis:
>>> C().c
1
>>> C.CSub.C_SUB_VALUE = 2
>>> C().c
2
Insgesamt bezieht sich diese Beschreibung und das getestete Verhalten auf Python 3.4. Ein Test mit 2.x wurde nicht vorgenommen. Den gesamten Beispiel-Code und einen passenden Satz Unittests gibt es als Gist: https://gist.github.com/edelbluth/7423e2e34bcd7447b1be.
Autor:
Jürgen Edelbluth
Themen:
Software-Entwicklung Python
Veröffentlicht:
16.06.2015 20:11
Zuletzt aktualisiert:
13.03.2020 16:57