Seite 1 von 1

Klassenattribut als Methodenaufruf

Verfasst: Mittwoch 3. Juni 2009, 14:40
von snafu
Hi,

ich habe folgenden Pseudocode für eine Klasse:

Code: Alles auswählen

class Foo(object):
    def __init__(self):
        self.say = self.say(what)
    def say(self, what):
        return 'I say:', what
In anderen Worten, ich möchte für's Setzen und Abfragen des Attributs `say` eine Klassenmethode - hier: `say()` - aufrufen. Da die gleichen Namen wohl kollidieren, von mir aus auch `say_something()`.

Um das Ganze mal konkreter zu zeigen:

Code: Alles auswählen

In [26]: class Foo(object):
   ....:     def __getattr__(self, attr):
   ....:         if attr == 'bar':
   ....:             return 'baz'
   ....:         
   ....:         

In [27]: foo = Foo()

In [28]: foo.bar
Out[28]: 'baz'
...funktioniert wie gewünscht. Aber sobald ich einen neuen Wert für das Attribut setze, geht die Funktionalität verloren:

Code: Alles auswählen

In [29]: foo.bar = 'spam'

In [30]: foo.bar
Out[30]: 'spam'
Kann mir vielleicht hier jemand die Hintergründe erklären und einen besseren Weg zeigen?

Mein Ansatz ist bisher dieser:

Code: Alles auswählen

In [128]: class Foo(object):
    def __getattr__(self, attr):
        if attr == 'bar':
            return 'baz'
        return getattr(self, attr)
    def __setattr__(self, attr, value):
        if attr == 'bar':
            pass
        else:
            setattr(self, attr, value)
   .....:             
   .....:             

In [138]: foo = Foo()

In [139]: foo.bar
Out[139]: 'baz'

In [140]: foo.bar = 'spam'

In [141]: foo.bar
Out[141]: 'baz'
Wenn ich jetzt aber den Wert für ein anderes Attribut neu setzen will (z.B. `foo.ham = 'spam'`), führt dies zu einer endlosen Rekursion. Mir ist nicht ganz klar, warum das Verhalten bei `__setattr__()` und `__getattr__()` so verschieden ist. Und ich hätte gerne erst das geklärt, bevor ich mich an den Methodenaufruf wage.

EDIT: Die zweite Methode lässt sich natürlich einfacher ausdrücken mit `if not attr == 'bar':`.

Verfasst: Mittwoch 3. Juni 2009, 14:51
von Dill

Code: Alles auswählen

class Foo(object):
    def __getattr__(self, attr):
        if attr == 'bar':
            return 'baz'
        return getattr(self, attr)
    def __setattr__(self, attr, value):
        if attr == 'bar':
            pass
        else:
            setattr(self, attr, value)
für "bar" hast du jeweils einen rekursionsabbruch. (zeilen 4+8 )
für alle anderen attribute wird die funktion wieder aufgerufen. (zeilen 6+11) ohne dass du jemals den abbruch erreichen könntest.

edit: was meinst du damit, dass sich setattr und getattr unterschiedlich verhalten würden?

Verfasst: Mittwoch 3. Juni 2009, 15:00
von snafu
Okay, habe mich wahrscheinlich ungenau ausgedrückt: Warum führt ein `setattr()`, das innerhalb der `__setattr__()`-Methode meiner Klasse steht, immer wieder die `__setattr__()`-Methode meiner Klasse aus, während ein `getattr()`, das innerhalb der `__getattr__()`-Methode meiner Klasse steht, offenbar direkt auf das Attribut zugreift?

Denn sowas hier ist ja kein Problem:

Code: Alles auswählen

In [162]: class Foo(object):
    def __getattr__(self, attr):
        if attr == 'bar':
            return 'baz'
        return getattr(self, attr)
   .....:     
   .....:     

In [167]: foo = Foo()

In [168]: foo.bar
Out[168]: 'baz'

In [169]: foo.ham = 'spam'

In [170]: foo.ham
Out[170]: 'spam'

Verfasst: Mittwoch 3. Juni 2009, 15:02
von helduel
Moin,

warum kein Property?

Gruß,
Manuel

Verfasst: Mittwoch 3. Juni 2009, 15:05
von Dill
ich hab das jetzt mal getestet und ich bekomme auch bei getattr eine endlosrekursion.

Verfasst: Mittwoch 3. Juni 2009, 15:05
von helduel
snafu hat geschrieben:Okay, habe mich wahrscheinlich ungenau ausgedrückt: Warum führt ein `setattr()`, das innerhalb der `__setattr__()`-Methode meiner Klasse steht, immer wieder die `__setattr__()`-Methode meiner Klasse aus, während ein `getattr()`, das innerhalb der `__getattr__()`-Methode meiner Klasse steht, offenbar direkt auf das Attribut zugreift?
Das führt ebenso zu einer endlosen Rekursion. Warum das in deinem Beispiel funktioniert liegt daran, dass __getattr__ nur dann aufgerufen wird, wenn es das Attribut nicht gibt. Setzt zu erst das Attribut (wie in deinem Beispiel) und willst es dann lesen, dann wird __getattr__ gar nicht aufgerufen.

Gruß,
Manuel

Verfasst: Mittwoch 3. Juni 2009, 15:44
von snafu
Danke, Manuel. :)

Mit `property` hatte ich mich bisher nie wirklich auseinandergesetzt. Das `say`-Beispiel ist zwar nicht sehr realistisch, aber ich denke, ich habe das Prinzip jetzt verstanden.

Code: Alles auswählen

>>> class Blabbler(object):
...     def __init__(self):
...         self.words = ''
...     @property
...     def say(self):
...         if not self.words:
...             return 'I say nothing.'
...         return 'I say: %s' % self.words
...     @say.setter
...     def say(self, value):
...         self.words = value
...
>>> blabbler = Blabbler()
>>> blabbler.say
'I say nothing.'
>>> blabbler.say = 'Ham with spam and eggs, please!'
>>> blabbler.say
'I say: Ham with spam and eggs, please!'

Verfasst: Mittwoch 3. Juni 2009, 20:00
von str1442
* Kleine Anmerkung: Es ist eine normale Methode, keine *Klassen*methode - die erstellt man mit classmethod().

Willst du mit __setattr__, __delattr__ usw arbeiten, musst du super(<Class>, self).__setattr__(<arguments>) usw benutzen um die (Standard-)Implementation der Superklasse zu benutzen.