Klassenattribut als Methodenaufruf

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
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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':`.
Benutzeravatar
Dill
User
Beiträge: 470
Registriert: Mittwoch 10. Januar 2007, 14:52
Wohnort: Köln

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?
http://www.kinderpornos.info
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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'
Zuletzt geändert von snafu am Mittwoch 3. Juni 2009, 15:02, insgesamt 1-mal geändert.
Benutzeravatar
helduel
User
Beiträge: 300
Registriert: Montag 23. Juli 2007, 14:05
Wohnort: Laupheim

Moin,

warum kein Property?

Gruß,
Manuel
Benutzeravatar
Dill
User
Beiträge: 470
Registriert: Mittwoch 10. Januar 2007, 14:52
Wohnort: Köln

ich hab das jetzt mal getestet und ich bekomme auch bei getattr eine endlosrekursion.
http://www.kinderpornos.info
Benutzeravatar
helduel
User
Beiträge: 300
Registriert: Montag 23. Juli 2007, 14:05
Wohnort: Laupheim

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
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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!'
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

* 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.
Antworten