__getattr__ und __setattr__ usw

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
ChrisGTJ
User
Beiträge: 105
Registriert: Mittwoch 22. August 2007, 15:45

Hallo,

ich möchte den Zugriff auf die Attribute einer Klasse kontrollieren, und zwar den Lesezugriff *und* den Schreibzugriff. Suchen und Lesen bringt einen schnell auf die Methoden __get/setattr__ und __get/setattribute__. Leider habe ich irgendwas nicht verstanden oder ich will etwas völlig blödsinniges tun:

Zunächst mal nur die Schreibkontrolle:

Code: Alles auswählen

class SetGetAttr_overwritten( object):
    def __init__( self):
        self.attr1 = 7
        print self.__dict__

    def __setattr__(self, name, value):
        print "__setattr__"
        if name in self.__dict__:
            print "Attributschreiben: " + name
            self.__dict__[name] = value
        else:
            print "Attribut anlegen: " + name
            self.__dict__[name] = value


obj = SetGetAttr_overwritten()
print obj
print "Attribut1: " + str( obj.attr1)
obj.attr1 = 1
print "Attribut1: " + str( obj.attr1)

Die Ausgabe ist folgendes:

Code: Alles auswählen

Attribut anlegen: attr1
{'attr1': 7}
<__main__.SetGetAttr_overwritten object at 0x02DC4AF0>
Attribut1: 7
Attributschreiben: attr1
Attribut1: 1
Soweit, so gut. Das Beschreiben und Anlegen eines Attributs ist also möglich.

Jetzt die Kontrolle des Attributlesens:

Code: Alles auswählen

class SetGetAttr_overwritten( object):
    def __init__( self):
        self.attr1 = 7
        print self.__dict__

    def __getattr__(self, name):
        if name in self.__dict__:
            print "Attributlesen: " + name
            return self.__dict__[name]
        else:
            return "Attribut nicht vorhanden (lesen): " + name


obj = SetGetAttr_overwritten()
print obj
print "Attribut1: " + str( obj.attr1)
obj.attr1 = 1
print "Attribut1: " + str( obj.attr1)
print "Attribut3: " + str( obj.attr3)

Ausgabe:

Code: Alles auswählen

{'attr1': 7}
<__main__.SetGetAttr_overwritten object at 0x02DC4AF0>
Attribut1: 7
Attribut1: 1
Attribut3: Attribut nicht vorhanden (lesen): attr3
Zu sehen ist, daß __getattr__() verwendet wird, wenn das Attribut nicht im Dictionary __dict__ ist. Befindet es sich im __dict__, wird einfach das Attribut zurückgegeben, ohne __getattr__() aufzurufen.

Mein Ziel ist es aber auch die Lesezugriffe zu kontrollieren, aber __getattribute__() ist auch keine Hilfe, da hier auch der Zugriff auf __dict__ abgefangen wird. Das bedeutet natürlich, daß die erste Zeile von

Code: Alles auswählen

    def __getattribute__(self, name):
        if name in self.__dict__:
            print "Attributlesen: " + name
            return self.__dict__[name]
        else:
            return "Attribut nicht vorhanden (lesen): " + name


eine schöne Rekursion anfängt. Was also tun?

Hintergrund:

Eine Klasse beschreibt den Inhalt bzw die Bedeutung einer Anzahl von Bytes. Die Größe der Datenblöcke ist bekannt und immer gleich, die Bedeutungen sind aber je nach Zusammenhang anders.

Benutzt werden soll es dann so (wie die Blöcke an ihre Daten kommen, lasse ich jetzt mal weg):

Code: Alles auswählen

block1 = DataBlock1()
block2 = DataBlock2()
block1.part2 = 1
if block2.teil2:
    block1.part1 = 3
else:
    block1.part1 = 1
...

Ein Ziel ist es, mit relativ einfachen Mitteln die Blockbedeutungen zu definieren. Ein weiteres Ziel ist, daß die einzelnen Namen eines Blocks fest definiert sind und nicht über Strings angegeben werden müssen (daher soll es eben eine Klasse werden). Im Beispiel müssen nur die Namen, Masken und Positionen angepaßt werden:

Code: Alles auswählen

class DataBlock1( BlockParent):
    def __init__(self, blockName):
        super( DataBlock1, self).__init__(blockName = blockName)
        self.part1 = BitBlockDef( name = "Part1", pos = 0, mask = 0x3)
        self.part2 = BitBlockDef( name = "Part2", pos = 2, mask = 0x4)
        self.part3 = BitBlockDef( name = "Part3", pos = 3, mask = 0x8)
        self.reserved = BitBlockDef( name = "res", pos = 4, mask = 0xf0)

class DataBlock2( BlockParent):
    def __init__(self, blockName):
        super( DataBlock1, self).__init__(blockName = blockName)
        self.teil1 = BitBlockDef( name = "Teil1", pos = 0, mask = 0xf)
        self.teil2 = BitBlockDef( name = "Teil2", pos = 4, mask = 0x10)
        self.reserved = BitBlockDef( name = "res", pos = 5, mask = 0xe0)


Die Bitschieberei und Maskiererei wird in der Elternklasse weggekapselt (__get__ und __set__ sind die gewünschten Methoden...):

Code: Alles auswählen


class BitBlockDef( object):
    def __init__(self, name = "Part1", pos = 0, mask = 0x3):
        self.name = name
        self.pos  = pos
        self.mask = mask

class BlockParent( object):

    def __init__( self, blockName = blockName):
        self.blockName = blockName
        self._dataList = [0,0,0,0]
        
    def __get__( self, name, value)
        if name not in self.__dict__:
            raise
        else:
            attr = self.__dict__[name]
            if isinstance( attr, BitBlockDef):
                # Dieses greift nur auf das erste Byte der Daten zu:
                tmp = self.__dict__["_dataList"][0]
                val |= ((value & attr.mask) >> attr.pos) 
            elif isinstance( attr, BytBlockDef):
                print "Zugriff auf Bytebereich..."
            else:
                print "Ungültiger Zugriff: %s!" % name

    def __set__( self, name, value)
        if name not in self.__dict__:
            # Attribut prüfen und anlegen (eigentlich sollen nur "BitBlockDef" und "BytBlockDef" erlaubt sein...)
            self.__dict__[name] = value
        else:
            attr = self.__dict__[name]
            if isinstance( attr, BitBlockDef):
                # Dieses greift nur auf das erste Byte der Daten zu:
                tmp = self.__dict__["_dataList"][0]
                tmp |= ((value << attr.pos) & attr.mask)
                self.__dict__["_dataList"][0] = tmp
            elif isinstance( attr, BytBlockDef):
                print "Zugriff auf Bytebereich..."
            else:
                print "Ungültiger Zugriff: %s!" % name


So, vielleicht hat einer / eine von denen, die bis hier unten hin gekommen sind, eine Idee, wie ich das Problem lösen kann. Das fänd ich prima ... :)

Gruß und Danke,

Christoph
Benutzeravatar
birkenfeld
Python-Forum Veteran
Beiträge: 1603
Registriert: Montag 20. März 2006, 15:29
Wohnort: Die aufstrebende Universitätsstadt bei München

Ich denke, du suchst property().
Dann lieber noch Vim 7 als Windows 7.

http://pythonic.pocoo.org/
ChrisGTJ
User
Beiträge: 105
Registriert: Mittwoch 22. August 2007, 15:45

birkenfeld hat geschrieben:Ich denke, du suchst property().
Nein, weil ich dann für jedes Attribut eigene Property-Methoden anlegen müßte, oder? Die Attribute sollen ja beliebige Namen haben können.

Es sei denn, ich könnte je eine generische Property-Set und -Get Methode haben. ie müßte dann aber herausfinden können, auf welches Property (Attribut) sie zugreifen soll. Die Lösung sehe ich so nicht.

Trotzdem: Danke! :)

Gruß,

Christoph
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

__getattr__ / __setattr__ werden nur bei Zugriff auf nicht gefundene Attribute aufgerufen. __getattribute__ wird immer für jedes Attribut aufgerufen und ist eigentlich eine (zugänglich gemachte) recht interne Funktion und in 99% der Fälle nicht das, was man sucht. __get__ und __set__ definieren Deskriptoren, diese werden aufgerufen sobald eine Klasse versucht auf das Objekt, welche sie definiert, zuzugreifen. Das Objekt und dessen Klasse wird mitgegeben. name / value ist also als Attribut schonmal ganz falsch. Ist das wirklich, was du willst? Bei deiner Anwendung wird bei dem Auslesen von einer Instanz von BlockParent versucht, den Ausleser in dem dict der Instanz zu finden (was schonmal nie der Fall sein kann, da ein ein Namensraum dict per Definition nur Strings als Keys enthält) und dann mit dem gefundenen Wert zu testen, ob er denn ein BitBlockdef ist. Hä? raise kann nicht allein stehen.

Für bestimmte Attribute definierst du entsprechende Methoden und benutzt property() um dein Ziel zu erreichen.

ADD:

Ja klar geht das . Du kannst doch verschiedene properties anlegen und die gleiche Funktion benutzen. Einfach ausprobieren. Oder du kannst dir, wenn es angepasst werden soll, einen Funktionserzeuger erstellen und diesen dann benutzen. Einfach eine Funktion erstellen, die intern eine Funktion erstellt (in Abhängigkeit von Bedingungen usw, sonst machsts ja keinen Sinn) und diese zurückgeben.
Benutzeravatar
birkenfeld
Python-Forum Veteran
Beiträge: 1603
Registriert: Montag 20. März 2006, 15:29
Wohnort: Die aufstrebende Universitätsstadt bei München

ChrisGTJ hat geschrieben:
birkenfeld hat geschrieben:Ich denke, du suchst property().
Nein, weil ich dann für jedes Attribut eigene Property-Methoden anlegen müßte, oder? Die Attribute sollen ja beliebige Namen haben können.

Es sei denn, ich könnte je eine generische Property-Set und -Get Methode haben. ie müßte dann aber herausfinden können, auf welches Property (Attribut) sie zugreifen soll. Die Lösung sehe ich so nicht.
Dann baust du dir einen eigenen Deskriptor, der sich ähnlich wie property() verhält. Deine Namen __get__ und __set__ stimmen dann schon mal, du musst sie nur in "BitBlockDef" verschieben. Würde dann so ungefähr aussehen:

Code: Alles auswählen

class BitBlockDef(object):
    def __init__(self, attr = "Part1", pos = 0, mask = 0x3):
        self.attr = attr
        self.pos  = pos
        self.mask = mask
    def __get__(self, inst, obj):
        if self.attr not in inst.__dict__:
            raise AttributeError("%s not set" % self.attr)
        return (inst.__dict__[self.attr] & self.mask) >> self.pos
    def __set__(self, inst, value):
        if self.attr not in inst.__dict__:
            inst.__dict__[self.attr] = 0  # oder wie auch immer defaults behandelt werden sollen
        inst.__dict__[self.attr] |= ((value << self.pos) & self.mask)

class UsesBitBlockDef(object):
    block1 = BitBlockDef("intval", 0, 3)
    block2 = BitBlockDef("intval", 2, 1)

    def __init__(self, value):
        self.intval = value
Benutzung:

Code: Alles auswählen

x = UsesBitBlockDef()
x.intval = 127
print x.block1
x.block1 = 1
Dann lieber noch Vim 7 als Windows 7.

http://pythonic.pocoo.org/
ChrisGTJ
User
Beiträge: 105
Registriert: Mittwoch 22. August 2007, 15:45

str1442 hat geschrieben:__getattr__ / __setattr__ werden nur bei Zugriff auf nicht gefundene Attribute aufgerufen. __getattribute__ wird immer für jedes Attribut aufgerufen und ist eigentlich eine (zugänglich gemachte) recht interne Funktion und in 99% der Fälle nicht das, was man sucht.
Genau so ist es, leider.

str1442 hat geschrieben: __get__ und __set__ definieren Deskriptoren, diese werden aufgerufen sobald eine Klasse versucht auf das Objekt, welche sie definiert, zuzugreifen. Das Objekt und dessen Klasse wird mitgegeben. name / value ist also als Attribut schonmal ganz falsch. Ist das wirklich, was du willst?
Nee, natürlich nicht. Ich wußte nicht, daß es __get__ und __set__ gibt. Es sollten Platzhalter sein.

str1442 hat geschrieben: Bei deiner Anwendung wird bei dem Auslesen von einer Instanz von BlockParent versucht, den Ausleser in dem dict der Instanz zu finden (was schonmal nie der Fall sein kann, da ein ein Namensraum dict per Definition nur Strings als Keys enthält) und dann mit dem gefundenen Wert zu testen, ob er denn ein BitBlockdef ist. Hä?
? Ich komme nicht mit. Ich suche den Namen des Attributs im Dict und benutze dann den Inhalt des Attributs (die Definition des Werts, also Name, Position und Maske), um auf die Daten des Blocks zuzugreifen.
str1442 hat geschrieben: raise kann nicht allein stehen.
Richtig, aber bei dem Abschnitt handelt es sich um Pseudocode, sozusagen.
str1442 hat geschrieben: Für bestimmte Attribute definierst du entsprechende Methoden und benutzt property() um dein Ziel zu erreichen.

ADD:

Ja klar geht das . Du kannst doch verschiedene properties anlegen und die gleiche Funktion benutzen. Einfach ausprobieren.
Wenn ich Properties mit je einer zentralen get/set Methode nutze, weiß ich doch immer noch nicht, welches Property gemeint ist. Ziel der Aktion ist es ja auch, das Layout der Blockbeschreibung listenartig zu haben, damit leicht und vor allem übersichtlich neue Blöcke definiert werden können.
str1442 hat geschrieben: Oder du kannst dir, wenn es angepasst werden soll, einen Funktionserzeuger erstellen und diesen dann benutzen. Einfach eine Funktion erstellen, die intern eine Funktion erstellt (in Abhängigkeit von Bedingungen usw, sonst machsts ja keinen Sinn) und diese zurückgeben.
Oh je, da muß ich gestehen, daß mein Pytonesisch zu schlecht ist, um das umsetzen zu können. Wichtig ist außerdem, daß die Attribute per Zuweisung zugegriffen werden können (damit der Code übersichtlich bleibt).

Gruß,

Christoph
ChrisGTJ
User
Beiträge: 105
Registriert: Mittwoch 22. August 2007, 15:45

birkenfeld hat geschrieben:
Dann baust du dir einen eigenen Deskriptor, der sich ähnlich wie property() verhält. Deine Namen __get__ und __set__ stimmen dann schon mal, du musst sie nur in "BitBlockDef" verschieben.
Sowas fiehl mir eben auch ein. Im Prinzip überlädt man so den Zuweisungsoperator, verstehe ich das richtig? Ich glaube, wenn ich die Frage gleich so gestellt hätte, hätte ich nicht so viel schreiben müssen...

Danke, ich werde es mal probieren!

Gruß,

Christoph
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

Schau dir mal dieses Tutorial zu Deskriptoren hier an: http://home.arcor.de/brutus_/text/descriptor.html

Ein Objekt kann nur Namen als __dict__ keys haben. Es definiert ja grade seine Attributsnamen -> Attributsobjekte in diesem dict. Es kann keine andere Keys als Strings haben.
Oh je, da muß ich gestehen, daß mein Pytonesisch zu schlecht ist, um das umsetzen zu können.
Ganz einfaches Beispiel:

Code: Alles auswählen

In [9]: class A(object):
   ....:     def make_specific_prop(name):
   ....:         def get_prop(self):
   ....:             return getattr(self, name)
   ....:         def set_prop(self, value):
   ....:             setattr(self, name, value % 2 ** 8 - 1)
   ....:         return get_prop, set_prop
   ....:     first = property(*make_specific_prop("_first"))
   ....:     second = property(*make_specific_prop("_second"))
   ....:     def __init__(self):
   ....:         self._first, self._second = 0, 0
   ....:     del make_specific_prop    
   ....:         


   ....:     
   ....:     

In [23]: a = A()

In [24]: a.first = 5

In [25]: a.second = 277

In [26]: print a.first, a.second
4 20
Durch das del am Schluss sorge ich dafür, das meine "Konstruktionsfunktion" am Ende aus dem Klassenkörper verschwindet. Solange dieser nicht fertig ausgewertet ist, ist es ja noch keine Methode. Nachdem er ausgewertet wurden ist, werden erst Funktionen in Methoden umgewandelt.
BlackJack

@ChrisGTJ: Kennst Du die Construct Bibliothek? Sieht nämlich so ein bisschen danach aus, als wolltest Du das Rad neu erfinden.
ChrisGTJ
User
Beiträge: 105
Registriert: Mittwoch 22. August 2007, 15:45

BlackJack hat geschrieben:@ChrisGTJ: Kennst Du die Construct Bibliothek? Sieht nämlich so ein bisschen danach aus, als wolltest Du das Rad neu erfinden.
Hallo BlackJack,

danke für den Tip! Ich habe trotzdem erstmal den Vorschlag von Birkenfeld implementiert.

Gruß und danke an alle!

Christoph
Antworten