Seite 1 von 1
`__getattribute__` Rekursion umgehen?
Verfasst: Dienstag 22. April 2014, 12:48
von mutetella
Hallo,
grundsätzlich ist mir klar, wie ich die "Rekursionsfalle" beim Überschreiben der `__getattribute__` umgehe:
Code: Alles auswählen
>>> class NoRecursion(object):
... def __init__(self):
... self.attribute = "Here I'am!"
... def __getattribute__(self, name):
... try:
... return object.__getattribute__(self, name)
... except AttributeError:
... return 'Where are you?'
...
>>> nr = NoRecursion()
>>> nr.attribute
"Here I'am!"
>>> nr.another
'Where are you?'
Wenn ich nun dasselbe auf ein Attribute eines Attributes anwenden möchte, weiß ich nicht weiter:
Code: Alles auswählen
>>> class NoRecursion(object):
... def __init__(self):
... self._datetime = datetime.datetime.now()
... self.attribute = "Here I'am!"
... def __getattribute__(self, name):
... try:
... return datetime.datetime.__getattribute__(self._datetime, name)
... except AttributeError:
... try:
... return object.__getattribute__(self, name)
... except AttributeError:
... raise
...
Funktioniert natürlich nicht, da der Zugriff auf `self._datetime` wiederum auf `__getattribute__` zugreift... Wie müsste ein solches Gebilde aussehen? Letztlich möchte ich erreichen, dass `name` zuerst in `self._datetime` abgefragt wird und wenn dort nicht vorhanden in `self`.
mutetella
Re: `__getattribute__` Rekursion umgehen?
Verfasst: Dienstag 22. April 2014, 13:26
von BlackJack
@mutetella: Das ist eine eigenartige Konstruktion, warum willst Du so etwas denn überhaupt machen? Ich glaube ich habe auch noch gar nicht verstanden *was* Du da eigentlich machen willst? Vielleicht irgendwie zu viel Magie?
Re: `__getattribute__` Rekursion umgehen?
Verfasst: Dienstag 22. April 2014, 13:58
von mutetella
@BlackJack
Ich habe eine eigene `DateTime` Klasse, die sich letzten Endes wie ein `datetime` Objekt verhalten soll allerdings zusätzlich noch ein paar Methoden und eine eigene `__format__` Methode hat. Ich möchte nicht von `datetime` erben, da ich beim Addieren und Subtrahieren wiederum keine `datetime`- sondern `DateTime`-Objekte zurückbekommen möchte. Bisher schaut das ganze so aus:
Code: Alles auswählen
class DateTime(object):
def __init__(self, dt_values, dawn=None):
'''__init__(dt_values[, dawn])
__init__(DateTime)
The given argument(s) must be either a tuple/list
and/or `dawn` which must be True or False or a
`DateTime` instance in which case a copy of the
same will be created.
If `dt_values` only has 3 elements for year, month
and date the `dawn` flag must be given otherwise a
ValueError occurs. This flag indicates whether the
`DateTime` instance become time values of (0, 0) if
`dawn` is True or (23, 59) if False. In both cases
the `has_time` flag of the instance will be False.
If `dt_values` has 5 elements this values will be
used to create the datetime. Thereby no `dawn` is
needed.
If `dt_values` has more or less than 3 or 5 elements
a ValueError occurs. '''
if not isinstance(dt_values, DateTime):
if len(dt_values) == 3:
if dawn is None:
raise ValueError('Can\'t create a {!r} instance '
'without time values and missing '
'`dawn` flag.'.format(
self.__class__.__name__))
hour, minute = (0, 0) if dawn else (23, 59)
self._datetime = datetime.datetime(*dt_values,
hour=hour, minute=minute)
self.has_time = False
elif len(dt_values) == 5:
self._datetime = datetime.datetime(*dt_values)
self.has_time = True
else:
raise ValueError('Wrong argument amount to create '
'{0!r} instance: {1!r}'.format(
self.__class__.__name__, dt_values))
else:
self._datetime = dt_values._datetime
self.has_time = dt_values.has_time
@property
def hour(self):
return self._datetime.hour
@property
def minute(self):
return self._datetime.minute
@property
def day(self):
return self._datetime.day
@property
def month(self):
return self._datetime.month
@property
def year(self):
return self._datetime.year
@classmethod
def now(cls, time=None):
if time is None:
return cls(*datetime.datetime.now().timetuple()[0:5])
return cls(
*datetime.datetime.now().timetuple()[0:3] +
((0, 0) if time == 'start' else (23, 59)),
has_time = False
)
def replace(self, **kwargs):
new = DateTime(self)
new._datetime = new._datetime.replace(**kwargs)
new.has_date = True
return new
def timetuple(self):
return self._datetime.timetuple()
def weekday(self):
'''weekday() -> Bundle object
Returns a bundle object which contains these attributes:
`weekday`: Weekday as number (0=Mo - 6=Su)
`position`: The number of the weekday within
month (1 - 5)
`is_last`: Is it the last occurence of the
weekday within month (True/False) '''
return utils.Bundle(
position=((self.day - 1) / 7) + 1,
is_last=(utils.month_len(self) - self.day) < 7,
weekday=self._datetime.weekday()
)
def get_raws(self):
return {'YmdHM': self.timetuple()[0:5], 'has_time': self.has_time}
def _validate_delta(self, delta):
if delta.seconds + delta.microseconds:
raise NotImplementedError(
'Counting with time values leading to non day exactly '
'resolution currently not implemented.'
)
def __add__(self, delta):
self._validate_delta(delta)
new = DateTime(self)
new._datetime = new._datetime + delta
return new
def __sub__(self, value):
if isinstance(value, datetime.timedelta):
self._validate_delta(value)
new = DateTime(self)
new._datetime = new._datetime - value
return new
return self._datetime - value._datetime
def __lt__(self, other):
return self._datetime < other._datetime
def __le__(self, other):
return self._datetime <= other._datetime
def __gt__(self, other):
return self._datetime > other._datetime
def __ge__(self, other):
return self._datetime >= other._datetime
def __eq__(self, other):
return self._datetime == other._datetime
def __ne__(self, other):
return self._datetime != other._datetime
def __format__(self, spec):
#Needed, cause datetime's strftime()-method
#has the following year-restrictions:
# < Python 3.1 year >= 1900 is required
# <= Python 3.2 year >= 1000 is required
# >= Python 3.3 no restriction
try:
return format(self._datetime, spec)
except ValueError:
for directive, value in zip(
(('%Y', '{:04d}'), ('%m', '{:02d}'), ('%d', '{:02d}'),
('%H', '{:02d}'), ('%M', '{:02d}')),
self._datetime.timetuple()[0:5]):
spec = spec.replace(directive[0], directive[1].format(value))
return spec
def __hash__(self):
return hash((self._datetime, self.has_time))
def __str__(self):
def_time = ('', '') if self.has_time else ('[', ']')
return ('{0:%Y-%m-%d '
'{def_time[0]}%H:%M{def_time[1]}}'.format(
self, def_time=def_time))
def __repr__(self):
def_time = ('', '') if self.has_time else ('[', ']')
return ('{0}.{1}({2:%Y-%m-%d '
'{def_time[0]}%H:%M{def_time[1]}})'.format(
self.__module__, self.__class__.__name__, self,
def_time=def_time))
Nun dachte ich mir eben, dass die ganzen `__eq__`, `__ne__`, `@property: year` etc. Definitionen wegfallen könnten, wenn ich einfach erstmal gewünschte Attribute auf `self._datetime` abfrage. Wenn eine Methode oder ein Attribut dort nicht gefunden wird, schaue ich auf `self` nach oder löse dann eine Exception aus.
mutetella
Re: `__getattribute__` Rekursion umgehen?
Verfasst: Dienstag 22. April 2014, 15:42
von Sirius3
@mutetella: explizit zu sagen, welche Attribute geerbt werden sollen, ist besser, als alles erstmal vom datetime-Objekt zu übernehmen. Da die Attribute ja nicht wirklich dynamisch sind. Spezialfunktionen __xxx__ müssen übrigens direkt in der jeweiligen Klasse implementiert sein, weil Python intern Optimierungen macht, die nur funktionieren, wenn Python auch weiß, welche Operatoren überschrieben sind.
Re: `__getattribute__` Rekursion umgehen?
Verfasst: Dienstag 22. April 2014, 19:25
von mutetella
@Sirius3
Wenn ich Dich richtig verstanden habe, dann ist meine Lösung, sofern ich nicht von `datetime.datetime` erben lasse, soweit in Ordnung und ich vergesse die `__getattribute__` Geschichte wieder?
Andererseits wollte ich ja nur deshalb nicht von `datetime.datetime` erben, weil ich nicht wusste, wie ich es dabei anstellen könnte, bei Methoden, die ein neues `datetime.datetime` Objekt zurückgeben, dieses durch ein neues `DateTime` Objekt zu ersetzen. Nun habe ich heute folgende Lösung gefunden, die eben dieses Problem löst:
Code: Alles auswählen
class DateTime(datetime.datetime):
def __new__(cls, dt_values):
if not isinstance(dt_values, DateTime):
return datetime.datetime.__new__(cls, *dt_values)
else:
return datetime.datetime.__new__(cls, *dt_values.timetuple()[0:5])
def replace(self, **kwargs):
new = super(DateTime, self).replace(**kwargs)
return DateTime(new.timetuple()[0:5])
def _validate_delta(self, delta):
if delta.seconds + delta.microseconds:
raise NotImplementedError(
'Counting with time values leading to non day exactly '
'resolution currently not implemented.'
)
def __add__(self, delta):
self._validate_delta(delta)
new = super(DateTime, self).__add__(delta)
return DateTime(new.timetuple()[0:5])
def __radd__(self, delta):
return self.__add__(delta)
def __sub__(self, value):
new = super(DateTime, self).__sub__(value)
if isinstance(value, datetime.timedelta):
self._validate_delta(value)
return DateTime(new.timetuple()[0:5])
return new
def __rsub__(self, value):
return self.__sub__(value)
def __format__(self, spec):
#Needed, cause datetime's strftime()-method
#has the following year-restrictions:
# < Python 3.1 year >= 1900 is required
# <= Python 3.2 year >= 1000 is required
# >= Python 3.3 no restriction
try:
return format(self._datetime, spec)
except ValueError:
for directive, value in zip(
(('%Y', '{:04d}'), ('%m', '{:02d}'), ('%d', '{:02d}'),
('%H', '{:02d}'), ('%M', '{:02d}')),
self._datetime.timetuple()[0:5]):
spec = spec.replace(directive[0], directive[1].format(value))
return spec
Was haltet ihr davon? Damit müsste ich nur die Methoden überschreiben bzw. neu schreiben, die ich auch tatsächlich berühren möchte.
Was ich halt leider immer noch nicht so wirklich verstehe, ist diese `super()` Sache...
mutetella
Re: `__getattribute__` Rekursion umgehen?
Verfasst: Dienstag 22. April 2014, 19:38
von BlackJack
@mutetella: Ich würde von der `super()`-Sache einfach die Finger lassen. Beispiel `replace()` (ungetestet):
Code: Alles auswählen
def replace(self, **kwargs):
return DateTime(datetime.datetime.replace(self, **kwargs))
Während man `__radd__()` so implementieren kann, weil die Addition kommutativ ist, dürfte das bei `__rsub__()` nicht gehen die Operanden zu vertauschen.

Re: `__getattribute__` Rekursion umgehen?
Verfasst: Mittwoch 23. April 2014, 09:51
von mutetella
@BlackJack
``17 - 10`` ist natürlich etwas anderes als ``10 - 17``, aber weshalb funktioniert das dann:
Code: Alles auswählen
def __sub__(self, value):
if isinstance(value, datetime.timedelta):
self._validate_delta(value)
return DateTime(
datetime.datetime.__sub__(self, value).timetuple()[0:5]
)
return datetime.datetime.__sub__(self, value)
def __rsub__(self, value):
return self.__sub__(value)
Code: Alles auswählen
In [84]: d = dt.DateTime((2014, 4, 17))
In [85]: d1 = dt.DateTime((2014, 4, 10))
In [86]: d - d1
Out[86]: datetime.timedelta(7)
In [87]: d1 - d
Out[87]: datetime.timedelta(-7)
Wenn ich das umschreibe, passiert nichts anderes, außer dass die nicht mögliche Variante ``timedelta - datetime`` nun auch einen Fehler wirft:
Code: Alles auswählen
def __sub__(self, value):
if isinstance(value, datetime.timedelta):
self._validate_delta(value)
return DateTime(
datetime.datetime.__sub__(self, value).timetuple()[0:5]
)
return datetime.datetime.__sub__(self, value)
def __rsub__(self, value):
if isinstance(value, datetime.timedelta):
# must raise a `TypeError`!
datetime.datetime.__rsub__(self, value)
return datetime.datetime.__rsub__(self, value)
Code: Alles auswählen
In [111]: d = dt.DateTime((2014, 4, 17))
In [112]: d1 = dt.DateTime((2014, 4, 10))
In [113]: d - d1
Out[113]: datetime.timedelta(7)
In [114]: d1 - d
Out[114]: datetime.timedelta(-7)
In [115]: d - _113
Out[115]: DateTime(2014, 4, 10, 0, 0)
In [116]: _113 - d
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-116-94a4866e422d> in <module>()
----> 1 _113 - d
TypeError: unsupported operand type(s) for -: 'datetime.timedelta' and 'DateTime'
Weshalb also funktionieren beide Varianten, abgesehen vom fehlenden `TypeError` in Variant 1?
BlackJack hat geschrieben:Ich würde von der `super()`-Sache einfach die Finger lassen.
Ich habe inzwischen ein paar Artikel über `super()` gelesen und versucht, diese zu verstehen. Mein Fazit: Ich nehme Deinen Rat an, auch wenn ich keine Ahnung habe, was es mit dieser `super()`-Sache eigentlich wirklich auf sich hat...
mutetella
Re: `__getattribute__` Rekursion umgehen?
Verfasst: Mittwoch 23. April 2014, 10:35
von mutetella
Also eigentlich kann ich die `__rsub__()` doch komplett weglassen. Die wird ja eh nur dann aufgerufen, wenn verschiedene Typen subtrahiert werden und dann wiederum nur, wenn meine `DateTime` rechts steht, was nicht erlaubt ist. Und diesen `TypeError` kann dann auch die `timedelta.__sub__` werfen.
Ist das, was ich schreibe, so richtig?
mutetella