# -*- coding: utf8 -*-

# Nutzung eines Overriding Descriptors

class OverridingDescriptor(object):
    'einfacher data/overriding descriptor'

    # ein non-overriding descriptor hat nur eine Methode __get__(), aber kein
    # __set__() und wird beim attribut lookup etwas anders behandelt

    # wir verwalten hier die Attributwerte in den Descriptor-Instanzen
    def __init__(self, val=0):
        self.val = val

    # __get__ wird beim attribute lookup gerufen
    def __get__(self, instance, cls):
        print('__get__ in descriptor: %s, %s, %s' % (self, instance, cls))
        # wir geben hier das Doppelte des gespeicherten Wertes zurück
        return 2 * self.val

    # __set__ wird beim attribute binding gerufen
    def __set__(self, instance, value):
        print('__set__ in descriptor: %s, %s, %s' % (self, instance, value))
        self.val = value

    # __del__ wird bei del gerufen
    def __delete__(self, instance):
        print('__delete__ in descriptor: %s, %s' % (self, instance))

# Definition einer Klasse, die den obigen Descriptor nutzt
class MyClass(object):
    x = OverridingDescriptor(4)
    y = 5

m = MyClass()
print(m.x)  # 8
print(m.y)  # 5

m.x = 40
print(m.x)  # 80

del m.x     # es wird nichts gelöscht
print(m.x)  # 80

# Analyse der Objekte

# das Instanz-Dictionary von m ist leer
print()
print('vars(m)', vars(m))

print()
# bei dir() sehen wir u.a. die beiden Klassenattribute x und y
print('dir(m)', dir(m))

print()
# dir() wird für das von der Descriptor-Methode __get__ gelieferte Objekt
# gerufen (im Beispiel also für 80)
print('dir(m.x)', dir(m.x))

print()
# analog zu dir(m.x), wobei der Zugriff hier über die Klasse statt über die
# Instanz erfolgt
print('dir(MyClass.x)', dir(MyClass.x))

print()
# beim Zugriff über das Klassen-Dictionary greifen wir direkt auf die
# Descriptor-Instanz zu und sehen, wie sie den Attributwert speichert; __get__
# wird hier also nicht gerufen
print("vars(MyClass.__dict__['x'])", vars(MyClass.__dict__['x']))
