Stephan Stephan - 1 month ago 7
Python Question

Access previous values of a class attribute

from collections import defaultdict


class History:
def __init__(self):
self.__dict__ = defaultdict()

def __getattr__(self,name):
if name[0] not in self.__dict__:
raise NameError
return self.__dict__[name]

def __getitem__(self,index):
pass

def __setattr__(self,name,value):
if name[0] in self.__dict__:
self.__dict__[name]= value
elif name[0] not in self.__dict__:
if '_prev' in name:
raise NameError
else:
self.__dict__[name]= value


if __name__ == '__main__':
# Put in simple tests for History before allowing driver to run

print()
import driver

driver.default_file_name = 'bsc2.txt'
# driver.default_show_traceback = True
# driver.default_show_exception = True
# driver.default_show_exception_message = True
driver.driver()


Instruction



Write the
__getattr__
method to allow the names of attributes with any number (one or more) of
_prev
suffixes to return the correct (previous, previous to previous, etc. hint:
str.count
method) value. If some number (one or more) of
_prev
does not appear as part of the suffix, or the name before the first
_prev
suffix is not an attribute of the class, raise a
NameError
exception with an appropriate string describing the problem/values. If the number of _prev suffixes is too large (there aren't that many previous values), this method should return the value
None
.

My question



I am working on the
__getattr__(self,name)
method. The correct results are listed followed:

e-->x.a-->3
^-->x.q__prev-->NameError # a new test
e-->x.a_prev-->2
e-->x.a_prev_prev-->1
e-->x.a_prev_prev_prev-->None
^-->x.c_prev_prev-->NameError


but I failed to return the value which has '_prev' in the end which got the following Error:

17 *Error: x.a_prev raised exception KeyError: 'a_prev'
18 *Error: x.a_prev_prev raised exception KeyError: 'a_prev_prev'
19 *Error: x.a_prev_prev_prev raised exception KeyError: 'a_prev_prev_prev'


Can someone tell me how to fix it? (function
__getattr__
)

Answer

If I understand correctly what you are trying to do, you want to (have to?) write a class that offers a history for all of its attributes.

To achieve this you need to keep a backup of all previous values. You can do this by using a second dictionary (I called mine history) and move the old values there before you overwrite them. If you want to access the history, count how many "_prev" suffixes you find and index the history list for the attribute.

class History:
    def __init__(self):
        self.history = dict()

    def __getattr__(self, name):
        # how many steps to go back
        offset = name.count("_prev")
        # keep only the attribute name. There most certainly is a better way to do this.
        attr_name = name.split("_prev")[0]

        if attr_name not in self.__dict__:
            raise NameError
        elif (attr_name not in self.history) and (offset > 0):
            # return None for values that do not have a history yet
            return None
        else:
            if offset > len(self.history[attr_name]):
                # return None for all queries running out of the history
                # like a_prev_prev_prev for only two set values
                return None
            else:
                # return the 'historic' value.
                # subtract one for the active value
                return self.history[attr_name][offset-1]

    def _save_old_value(self, name):
        if name in self.history:
            # attribute is already in the history
            # prepend the activ value it to the history list
            self.history[name].insert(0, self.__dict__[name])
        else:
            # attribute is not in the history
            # create new list in the history dict
            self.history[name] = [self.__dict__[name]]

    def __setattr__(self, name, value):
        if name in self.__dict__:
            # the attribute has already been set so you need to backup the value
            self._save_old_value(name)
            self.__dict__[name] = value
        elif name not in self.__dict__:
            # the attribute is new
            if '_prev' in name:
                # prevent attribute names with _prev suffix so
                # that the user can not destroy the history
                raise NameError
            else:
                # everything is fine, add the value
                self.__dict__[name] = value

For me this worked out. Hopefully this covers all of the edge cases:

h = History()
h.a = 1
h.a = 2
h.a = 3
h.a = 4

h.b = 17

h.a
4

h.a_prev
3

h.a_prev_prev
2

h.a_prev_prev_prev
1

h.a_prev_prev_prev_prev
None

h.b
17

h.b_prev
None

h.x
NameError

h.x_prev
NameError
Comments