Seite 1 von 1
__getattr__ wird nur bei nicht vorhandenen attr aufgerufen?
Verfasst: Freitag 14. Dezember 2012, 12:18
von mutetella
Hallo,
ich habe folgende Klasse gebastelt:
Code: Alles auswählen
class DatetimeArgs(object):
'''This class is used to store datetime arguments.
If a asked argument not exist or its boolean value is False
the default value which is the equivalent value of the
current datetime will be returned.
Each argument can only set once otherwise a ValueError occurs.'''
DATETIME_ARG_NAMES = ('year', 'month', 'day', 'hour', 'minute')
DELTA_ARG_NAMES = ('years', 'months', 'days', 'hours', 'minutes')
def __init__(self):
self._defaults = dict(zip((self.DATETIME_ARG_NAMES),
datetime.datetime.now().timetuple()[0:5]) +
zip(self.DELTA_ARG_NAMES,
(0, 0, 0, 0, 0)))
def __setattr__(self, name, value):
if not name in self.__dict__:
self.__dict__[name] = value
else:
raise ValueError(ERR_REPEATED_DATETIME_ARG.format(name))
def __getattr__(self, name):
print '__getattr__ called'
value = self.__dict__.get(name, self._defaults[name])
return value or self._defaults[name]
def get_dict(self, type_):
type_ = (self.DELTA_ARG_NAMES if type_ == 'deltas' else
self.DATETIME_ARG_NAMES)
return dict((name, getattr(self, name)) for name in type_)
Frage ich ein (noch) nicht vorhandenes Attribut ab, wird wie erwartet '__getattr__' aufgerufen und der jeweilige default-Wert zurückgegeben. Sobald ich allerdings ein Attribut setze, wird bei einer Abfrage desselben dieses zwar zurückgegeben, allerdings nicht über die '__getattr__'-Methode. Warum?
Code: Alles auswählen
>>> results = DatetimeArgs()
>>> results.day
__getattr__ called
14
>>> results.day = 15
>>> results.day
15
mutetella
Re: __getattr__ wird nur bei nicht vorhandenen attr aufgeruf
Verfasst: Freitag 14. Dezember 2012, 12:25
von lunar
@mutetella:
RTFM:
Called when an attribute lookup has not found the attribute in the usual places (i.e. it is not an instance attribute nor is it found in the class tree for self). name is the attribute name. This method should return the (computed) attribute value or raise an AttributeError exception.
Note that if the attribute is found through the normal mechanism, __getattr__() is not called. (This is an intentional asymmetry between __getattr__() and __setattr__().) This is done both for efficiency reasons and because otherwise __getattr__() would have no way to access other attributes of the instance. Note that at least for instance variables, you can fake total control by not inserting any values in the instance attribute dictionary (but instead inserting them in another object). See the __getattribute__() method below for a way to actually get total control over attribute access.
In dem Du das Attribut in ".__dict__" einfügst, wird es zum regulären Exemplarattribut, welches der Python-Interpreter unter Umgehung von "__getattr__()" direkt abfragt. DIe offensichtliche Lösung ist also wie beschrieben, das Attribut nicht in ".__dict__" abzulegen, sondern in einem anderen Exemplarattribut, i.e. "self._args[name] = value", wobei Du "self._args" natürlich in "__init__()" anlegen musst.
Ich sehe allerdings den Sinn dieser Klasse nicht. Wieso eine zusätzliche Indirektion, anstatt einfach gleich direkt "datetime" oder "timedelta"-Objekte zu erzeugen? Und selbst wenn, wieso überschreibst Du ".__getattr__()" und ".__setattr__()"?
Verzeihe mir, doch ich habe – mal wieder – das Gefühl, dass Du über Moskau nach Paris reisen möchtest…
Re: __getattr__ wird nur bei nicht vorhandenen attr aufgeruf
Verfasst: Freitag 14. Dezember 2012, 15:57
von mutetella
@lunar
Sorry für's nicht Lesen der Doku, ich war so von Fragezeichen umgeben dass ich erstmal um Hilfe rufen musste...
Ich versuch' mich jetzt schon 'ne Weile an Folgendem:
Code: Alles auswählen
class DatetimeArgs(object):
DATETIME_ARG_NAMES = ('year', 'month', 'day', 'hour', 'minute')
DELTA_ARG_NAMES = ('years', 'months', 'days', 'hours', 'minutes')
def __init__(self):
self._defaults = dict(zip((self.DATETIME_ARG_NAMES),
datetime.datetime.now().timetuple()[0:5]) +
zip(self.DELTA_ARG_NAMES,
(0, 0, 0, 0, 0)))
def __setattr__(self, name, value):
if not hasattr(object, name):
object.__setattr__(self, name, value)
else:
raise ValueError(ERR_REPEATED_DATETIME_ARG.format(name))
def __getattribute__(self, name):
print '__getattribute__ called'
try:
return object.__getattribute__(self, name) or \
self._defaults[name]
except AttributeError:
return self._defaults[name]
Das Problem dabei ist, dass ich die Prüfung, ob ein Attribut bereits gesetzt ist, daran scheitert, dass beim Zugriff auf ein nicht vorhandenes Attribut der Fehler (auf den ja 'hasattr' reagiert) bereits in der '__getattribute__' behandelt wird. Ich steh' jetzt also vor einem "entweder oder"...
Diese Lösung...
Code: Alles auswählen
class DatetimeArgs(object):
DATETIME_ARG_NAMES = ('year', 'month', 'day', 'hour', 'minute')
DELTA_ARG_NAMES = ('years', 'months', 'days', 'hours', 'minutes')
def __init__(self):
self._defaults = dict(zip((self.DATETIME_ARG_NAMES),
datetime.datetime.now().timetuple()[0:5]) +
zip(self.DELTA_ARG_NAMES,
(0, 0, 0, 0, 0)))
self._assigned = {}
def set(self, name, value):
if name in self._assigned:
raise ValueError(ERR_REPEATED_DATETIME_ARG.format(name))
else:
self._assigned[name] = value
def get(self, name):
print '__getattribute__ called'
value = self._assigned.get(name, self._defaults[name])
return value or self._assigned[name]
funktioniert natürlich, gefällt mir halt nicht so gut, weil 'get' und 'set' verwendet werden muss.
Ist es tatsächlich so, dass ich direkten Zugriff auf die Attribute so nicht regeln kann?
mutetella
Re: __getattr__ wird nur bei nicht vorhandenen attr aufgeruf
Verfasst: Freitag 14. Dezember 2012, 17:03
von lunar
@mutetella
Code: Alles auswählen
class DatetimeArgs(object):
#…
DATETIME_ARG_NAMES = ('year', 'month', 'day', 'hour', 'minute')
DELTA_ARG_NAMES = ('years', 'months', 'days', 'hours', 'minutes')
def __init__(self):
self._defaults = dict(zip((self.DATETIME_ARG_NAMES),
datetime.datetime.now().timetuple()[0:5]) +
zip(self.DELTA_ARG_NAMES,
(0, 0, 0, 0, 0)))
self._assigned = {}
def __setattr__(self, name, value):
if not name in self._assigned:
self._assigned[name] = value
else:
raise ValueError(ERR_REPEATED_DATETIME_ARG.format(name))
def __getattr__(self, name):
return self._assigned.get(name, self._defaults[name])
Wie kommst Du überhaupt darauf, nun ".__getattribute__()" zu verwenden?! Weder habe ich Dir dazu geraten, noch war ist das die Lösung, von der die Dokumentation spricht.
Nebenbei bemerkt findet sich in Deinen Beiträgen folgender Quelltext:
Code: Alles auswählen
value = self._assigned.get(name, self._defaults[name])
return value or self._assigned[name]
Dieses Konstrukt ist mindestens komisch, und eigentlich sogar ziemlich falsch. Der rechte Operand in der zweiten Zeile wird nur ausgewertet, wenn "name in self._assigned"
und "not self._assigned[name]" gilt, sprich wenn "name" auf einen "falschen" Wert wie "None" oder die leere Zeichenkette abgebildet wird. In diesem Fall aber gilt "value is self._assigned[name]" (da "value" in der ersten Zeile an den Inhalt von "self._assigned[name]" gebunden wird). Die zweite Zeile lautet dann also "value or value", was offensichtlich gleich "value" und noch dazu ziemlich sinnlos ist, da "value" ohnehin ein falscher Wert ist, so dass Du in diesem Fall auch gleich "None" zurückgegeben könntest.
Richtig falsch wird es, wenn "name not in self._assigned" gilt. Dann gilt ja "value is self._defaults[name]", da ".get()" ja auf das zweite Argument zurückfällt (es sei denn, es gibt schon vorher einen "KeyError", weil "name not in self._defaults"). Für jeden Schlüssel aus "self.DELTA_ARG_NAMES" aber ist "self._defaults[name] = 0". Die zweite Zeile lautet dann "0 or self._assigned[name]". Es wird also immer der rechte Operand ausgewertet. Der aber wirft dann immer einen "KeyError", da "name" ja nicht in "self._assigned" steht.
Was in Gottes Namen hast Du Dir dabei nur gedacht?!
Dein zweiter Beitrag erklärt im Übrigen noch immer nicht den eigentlichen Sinn und Zweck dieser Klasse. Warum übergibst Du die Argumente nicht einfach direkt an "datetime.datetime()" oder "datetime.timedelta()"? Wieso sammelst Du sie erst umständlich in einer separaten Klasse?
Re: __getattr__ wird nur bei nicht vorhandenen attr aufgeruf
Verfasst: Freitag 14. Dezember 2012, 20:15
von mutetella
lunar hat geschrieben:Code: Alles auswählen
value = self._assigned.get(name, self._defaults[name])
return value or self._assigned[name]
Was in Gottes Namen hast Du Dir dabei nur gedacht?!
Oh Mann, sorry, da habe ich Code gepostet, den ich kurz darauf folgendermaßen korrigiert habe...:
Code: Alles auswählen
value = self._assigned.get(name, self._defaults[name])
return value or self._defaults[name]
1. Zeile: Wenn das Attribute noch nicht zugewiesen wurde, nimm den default-Wert
2. Zeile: Falls der zugewiesene Wert 0 oder '' ist, nimm ebenfalls den default-Wert. Sind beide Werte 0, spielt's ja keine Rolle.
lunar hat geschrieben:Wieso sammelst Du sie erst umständlich in einer separaten Klasse?
Hatte ich ursprünglich auch so gemacht. Allerdings ...
1. ... stolperte ich dann über den Umstand, dass, wenn z. B. in der Argumentenliste das year-Attribut doppelt vorkommt, das vorderste durch das hinterste überschrieben wird. Kann unter bestimmten Umständen an anderer Stelle eine Fehlermeldung verursachen, die so dann nicht mehr nachvollziehbar ist und ...
2. ... nicht gesetzte Argumente durch defaults ersetzt werden müssen.
Beides möchte ich nicht während des Parsens machen. Das mit einer Klasse zu lösen geisterte mir schon länger im Kopf herum. AWie kommst Du überhaupt darauf, nun ".__getattribute__()" zu verwenden?! Weder habe ich Dir dazu geraten, noch war ist das die Lösung, von der die Dokumentation spricht.ls ich dann im parser aus dem dateutils-Modul sah, dass auch dort eine Result-Klasse verwendet wird, machte ich mich an die Arbeit...
lunar hat geschrieben:Wie kommst Du überhaupt darauf, nun ".__getattribute__()" zu verwenden?! Weder habe ich Dir dazu geraten, noch war ist das die Lösung, von der die Dokumentation spricht.
Nun, dann hab' ich die Doku wohl falsch verstanden.
Jedenfalls wollte ich neben der jetzigen Lösung mit 'get'/'set' ausprobieren, ob das Überprüfen auf doppelte Attributszuweisung und automatische default-Bestückung nicht doch direkt geht.
mutetella
Re: __getattr__ wird nur bei nicht vorhandenen attr aufgeruf
Verfasst: Samstag 15. Dezember 2012, 00:06
von lunar
mutetella hat geschrieben:Code: Alles auswählen
value = self._assigned.get(name, self._defaults[name])
return value or self._defaults[name]
Code: Alles auswählen
return self._assigned.get(name) or self._defaults[name]
Re: __getattr__ wird nur bei nicht vorhandenen attr aufgeruf
Verfasst: Samstag 15. Dezember 2012, 01:10
von BlackJack
Wobei das nur funktioniert solange der Default immer 0 ist. Dann könnte man aber auch gleich ``return self._assigned.get(name, 0)`` schreiben.
Re: __getattr__ wird nur bei nicht vorhandenen attr aufgeruf
Verfasst: Samstag 15. Dezember 2012, 01:47
von lunar
@BlackJack Uhm, "self._assigned.get(name, 0)" ist nicht dasselbe wie "self._assigned.get(name) or self._defaults[name]". Warum der Standardwert da 0 sein müsste, erschließt sich mir ebenfalls nicht.
Re: __getattr__ wird nur bei nicht vorhandenen attr aufgeruf
Verfasst: Samstag 15. Dezember 2012, 02:35
von BlackJack
@lunar: Wenn man 0 zuweist wird beim Abfragen nicht die 0 sondern der Standardwert zurückgegeben. Dieses scheinbar clevere ``or`` um etwas komplizierteren Code zu sparen, fällt in der Regel auf die Nase wenn der linke Ausdruck auch im „pythonisch-boole'schen” Sinne falsch sein darf.
Angenommen der Minutenzeiger steht auf der 42ten Minute:
Code: Alles auswählen
da = DatetimeArgs()
print da.minute # -> 42
da.minute = 23
print da.minute # -> 23
da.minute = 0
print da.minute # -> 42
Nicht das was man haben möchte.
Re: __getattr__ wird nur bei nicht vorhandenen attr aufgeruf
Verfasst: Samstag 15. Dezember 2012, 07:35
von Sirius3
Hallo mutetella,
warum überhaupt eine __getattr__-Method?
Code: Alles auswählen
class DatetimeArgs(object):
DATETIME_ARG_NAMES = ('year', 'month', 'day', 'hour', 'minute')
DELTA_ARG_NAMES = ('years', 'months', 'days', 'hours', 'minutes')
def __init__(self):
self.__dict__.update(zip(self.DATETIME_ARG_NAMES,
datetime.datetime.now().timetuple()))
self.__dict__.update(zip(self.DELTA_ARG_NAMES, (0, 0, 0, 0, 0)))
self.__dict__['_attr_set']=set()
def __setattr__(self, name, value):
if name in self._attr_set:
raise ValueError(ERR_REPEATED_DATETIME_ARG.format(name))
self._attr_set.add(name)
object.__setattr__(self,name,value)
Grüße
Sirius
Re: __getattr__ wird nur bei nicht vorhandenen attr aufgeruf
Verfasst: Samstag 15. Dezember 2012, 08:39
von BlackJack
@Sirius3: Warum ``self.__dict__['_attr_set']=set()``? Das kann man doch direkter als ``self._attr_set = set()`` schreiben.
Re: __getattr__ wird nur bei nicht vorhandenen attr aufgeruf
Verfasst: Samstag 15. Dezember 2012, 09:00
von mutetella
BlackJack hat geschrieben:Das kann man doch direkter als ``self._attr_set = set()`` schreiben.
Dann ist '_attr_set' aber beim setzen von sich selbst in der '__setattr__' nicht vorhanden.
Re: __getattr__ wird nur bei nicht vorhandenen attr aufgeruf
Verfasst: Samstag 15. Dezember 2012, 12:17
von lunar
@BlackJack Das ist mir klar, nur sehe ich das Problem nicht. mutetella hat dieses Verhalten explizit gewünscht:
mutetella hat geschrieben:Falls der zugewiesene Wert 0[...], nimm ebenfalls den default-Wert.
Re: __getattr__ wird nur bei nicht vorhandenen attr aufgeruf
Verfasst: Samstag 15. Dezember 2012, 12:32
von BlackJack
@lunar: Das hatte ich nicht gelesen. Und kann mir irgendwie nicht vorstellen warum man das möchte, dass ein völlig legitimer Wert für Minuten und Stunden nicht verwendet werden kann. Zumal das ohne deutliche Dokumentation sehr überraschend ist.
Re: __getattr__ wird nur bei nicht vorhandenen attr aufgeruf
Verfasst: Samstag 15. Dezember 2012, 14:26
von mutetella
BlackJack hat geschrieben:... warum man das möchte, dass ein völlig legitimer Wert für Minuten und Stunden nicht verwendet werden kann.
Das liegt daran, dass ich zum "schnell mal testen" immer nur ymd-Argumente verwendete. Inzwischen ist mir das natürlich um die Ohren geflogen...
Dank Eurer Hilfe (@Sirius3: Vielen Dank, das naheliegendste lag für mich mal wieder so fern...

) sieht das jetzt so aus:
Code: Alles auswählen
class DatetimeArgs(object):
'''This class is used to store datetime and
relativedelta arguments.
If an asked argument
- not exist or
- its boolean value is False (except for datetime
hour and/or minute argument for which a value
of zero is allowed)
the default value will be returned.
The default value for
- a datetime argument is the equivalent value
of the current datetime
- a relativedelta argument is zero.
Each argument can only set once otherwise a ValueError occurs.'''
DATETIME_ARG_NAMES = ('year', 'month', 'day', 'hour', 'minute')
DELTA_ARG_NAMES = ('years', 'months', 'days', 'hours', 'minutes')
def __init__(self):
self.__dict__.update(zip(self.DATETIME_ARG_NAMES,
datetime.datetime.now().timetuple()))
self.__dict__.update(zip(self.DELTA_ARG_NAMES, (0, 0, 0, 0, 0)))
self.__dict__['_attr_set'] = set()
def __setattr__(self, name, value):
if name in self._attr_set:
raise ValueError(ERR_REPEATED_DATETIME_ARG.format(name))
self._attr_set.add(name)
if value or value == 0 and name in ('hour', 'minute'):
object.__setattr__(self,name,value)
def get_dict(self, type_):
type_ = (self.DELTA_ARG_NAMES if type_ == 'deltas' else
self.DATETIME_ARG_NAMES)
return dict(((name, getattr(self, name)) for name in type_))
Während des parsens übergebe ich die Ergebnisse somit an 'DatetimeArgs' und hole mir dann mit 'DatetimeArgs.get_dict()' die jeweiligen Argumente zum Bilden eines datetime- und eines relativedelta-Objekts.
Falls alles gut geht...
mutetella