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.


Kommentare

Noch keine Kommentare.