4

I am new to class programming. I am trying to save initial attributes (which are a dictionary) of my object in its history, and then update history with the changes of attributes. Below the code:

import datetime
import pytz

class Property:
    """ Simple property class """ 

    @staticmethod
    def _current_time():
        utc_time = datetime.datetime.utcnow()
        return pytz.utc.localize(utc_time)
    
    def __init__(self, link, attributes):
        self._link = link
        self.__attributes = attributes
        self._history = [(Property._current_time(), attributes)]
        
    def update(self, new_attributes):
        def dictdiff(d1, d2):                                              
            return dict(set(d2.items()) - set(d1.items()))
        attr_change = dictdiff(self.__attributes, new_attributes) 
        self.__attributes.update(attr_change)
        self._history.append((Property._current_time(), attr_change))
        
    def show_attributes(self):
        return self.__attributes
    
    def show_history(self):
        # how to show changes in time?
        return self._history
    

prop = Property(1234, {"Price":3000, "Active":"Yes"})
prop.update({"Price":2500, "Active":"Yes"})
prop.update({"Active":"No"})
prop.show_history()

And output:

Out[132]: 
[(datetime.datetime(2018, 10, 28, 10, 24, 19, 712385, tzinfo=<UTC>),
  {'Price': 2500, 'Active': 'No'}),
 (datetime.datetime(2018, 10, 28, 10, 24, 19, 712385, tzinfo=<UTC>),
  {'Price': 2500}),
 (datetime.datetime(2018, 10, 28, 10, 24, 19, 712385, tzinfo=<UTC>),
  {'Active': 'No'})]

First item in history should actually be:

(datetime.datetime(2018, 10, 28, 10, 24, 19, 712385, tzinfo=<UTC>),
  {"Price":3000, "Active":"Yes"})

I tried this but without success. It seems that init function is updating history of initialization as attributes are updated, and in the meanwhile in the history at the first place I want to see attributes that were first-time initialized.

martineau
  • 119,623
  • 25
  • 170
  • 301
shaimar
  • 128
  • 1
  • 1
  • 10
  • 1
    Probably a bad duplicate, but your problem is basically the same: [How to copy a dictionary and only edit the copy](//stackoverflow.com/q/2465921) – Aran-Fey Oct 28 '18 at 10:55

2 Answers2

1

The problem is that you're modifying the dictionary in the history with this code:

self.__attributes.update(attr_change)

You're basically doing this:

>>> attributes = {}
>>> history = [attributes]
>>> attributes.update(foo=3)
>>> history
[{'foo': 3}]

This is easily fixed by storing a copy of the dictionary in the history:

self._history = [(Property._current_time(), attributes.copy())]

See also How to copy a dictionary and only edit the copy.

Aran-Fey
  • 39,665
  • 11
  • 104
  • 149
1

You want the first history entry be a copy of the attributes.

self._history = [(Property._current_time(), dict(attributes))]

As your code is now, the first history entry references the current attribute dictionary.