-1

When running below code, the dict shows unexpected behaviour

I a nutshell, I select a date in the past and add the information to a dict about that date. All is well until I print the resulting dict after all the iterations:

functioning code, you can C/V this into your shell and run it with dictTester():

from calendar import datetime as calendar_date
from datetime import datetime
def dictTester():
    breakdowns_dict = {}
    for row in range(2):
        temp_list = [row, None, None]
        while_true_counter = 0
        date_extracted = datetime.now() - calendar_date.timedelta(days=row)
        date_extracted = datetime.strptime(date_extracted.strftime('%Y-%m-%d'),'%Y-%m-%d')
        date_now = datetime.strptime(datetime.now().strftime('%Y-%m-%d'),'%Y-%m-%d')
        while True:
            if date_extracted > date_now:
                print('date > now')
                print('break')
                break
            if date_extracted == date_now:
                print('date == now')
                temp_list[1] = date_extracted.strftime('%Y-%m-%d')
                if while_true_counter > 0:
                    temp_list[2] = 'RIGHT'
                else:
                    temp_list[2] = 'PURE'
                print(temp_list)
            if date_extracted < date_now:
                print('date < now')
                temp_list[1] = date_extracted.strftime('%Y-%m-%d')
                if while_true_counter > 0:
                    temp_list[2] = 'MIDDLE'
                else:
                    temp_list[2] = 'LEFT'
                print(temp_list)
            if date_extracted not in breakdowns_dict:
                breakdowns_dict[date_extracted] = {}
                print('created entry')
            else:
                print('updated entry')
            print(date_extracted)
            print(len(breakdowns_dict[date_extracted]))
            breakdowns_dict[date_extracted][len(breakdowns_dict[date_extracted])] = temp_list
            print(breakdowns_dict[date_extracted][len(breakdowns_dict[date_extracted])-1])
            # next iteration
            date_extracted += calendar_date.timedelta(days=1)
            while_true_counter += 1    
    for bd_date in breakdowns_dict:
        for bd_entry in breakdowns_dict[bd_date]:
            print(bd_date.strftime('%Y-%m-%d'), breakdowns_dict[bd_date][bd_entry])

python shell output

$ date == now     
$ [0, '2021-03-18', 'PURE']     
$ created entry '2021-03-18' 0 = [0, '2021-03-18', 'PURE']    
$ date > now     
$ break     
$ date < now     
$ created entry 2021-03-17 0 = [1, '2021-03-17', 'LEFT']     
$ date == now    
$ updated entry '2021-03-18' 1 = [1,'2021-03-18', 'RIGHT']     
$ date > now break

So you would expect the result dict to be:

$ '2021-03-18' 0 = [0, '2021-03-18', 'PURE']
$ '2021-03-18' 1 = [1,'2021-03-18', 'RIGHT']
$ '2021-03-17' 0 = [1, '2021-03-17', 'LEFT']

But instead it prints:

$ '2021-03-18' [0, '2021-03-18', 'PURE'] 
$ '2021-03-18' [1, '2021-03-18', 'RIGHT'] 
$ '2021-03-17' [1, '2021-03-18', 'RIGHT'] 

and you can see more of this funky behaviour when increasing the row value

for 3 you should get a 'PURE' a 'LEFT'-'RIGHT' and a 'LEFT'-'MIDDLE'-'RIGHT' and so on, you get the idea

but that would be disregarding the adversary.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
hewi
  • 1,274
  • 3
  • 17
  • 32
  • 2
    It looks like you're storing the same `temp_list` object in your data structure repeatedly inside that loop. (If you didn't realize that would be a problem, you might want to read https://nedbatchelder.com/text/names.html) – user2357112 Mar 18 '21 at 08:59
  • You post code which is good, but you also need to post the code that drives it so we have a self-contained test case. – Allan Wind Mar 18 '21 at 09:02
  • Nope, incorrect, when going through the while loop, I update the temp list indices 1 and 2 with atualised info, and the append it to the dict – hewi Mar 18 '21 at 09:02
  • when you run the dictTester, this is what the code spits out in the console; with console, I mean python interpreter!! – hewi Mar 18 '21 at 09:03
  • 2
    @hewi Appending to the dict doesn't make a copy of `temp_list`. You're storing the same list in multiple dict entries. – Barmar Mar 18 '21 at 09:06
  • it does work, and its a neat little trick, cause if the list is empty, len == 0 so dict[0], if there is a dict[0] then the len is 1 and we use dict[1] ... cool nah – hewi Mar 18 '21 at 09:10
  • 1
    @hewi no. Python uses *neither* pass by reference nor pass by value. – juanpa.arrivillaga Mar 18 '21 at 09:11
  • Again, you *really* should read https://nedbatchelder.com/text/names.html – juanpa.arrivillaga Mar 18 '21 at 09:11
  • 1
    @hewi: Lists don't work like that. You didn't get an error because you used a dict instead of a list, but using a dict for contiguous integer indexes is a questionable decision. Using a list and `list.append` would make more sense. – user2357112 Mar 18 '21 at 09:12
  • so how would I make the temp_list values be appended to the proper dict entry? – hewi Mar 18 '21 at 09:12
  • https://stackoverflow.com/questions/8744113/python-list-by-value-not-by-reference – hewi Mar 18 '21 at 09:22
  • thank you so much all, I think I found it, @Barmar you where right, I need to pass the value, not the reference!! – hewi Mar 18 '21 at 09:23

1 Answers1

0

The simple bug was a pass by reference vs pass by value error, silly me

as answered here: python list by value not by reference

updating the code line:

breakdowns_dict[date_extracted][len(breakdowns_dict[date_extracted])] = temp_list

to

breakdowns_dict[date_extracted][len(breakdowns_dict[date_extracted])] = temp_list[:]

was all that was required

hewi
  • 1,274
  • 3
  • 17
  • 32
  • You are absolutely incorrect. Assignment has reference semantics in both cases. `[:]` is a slice, which creates *a copy*. It still has reference semantics. And **python does not use pass by reference nor pass by value**. Regardless of whether assignment always has reference semantics. – juanpa.arrivillaga Mar 18 '21 at 10:02