0

I have two functions of very similar construction. The first creates a 3D list of dicts and adds data (the data consisting of the values of the three iterators used to construct the list). The second simply displays the dict data. The issue is that data displayed by the second function is wrong. Instead of showing values of the iterators increasing as expected:

(0, 9, 19)
(0, 9, 20)
(0, 10, 0)
(0, 10, 1)
(0, 10, 2)

I end up with the maximum value of the iterators being displayed:

(0, 12, 20)
(0, 12, 20)
(0, 12, 20)
(0, 12, 20)
(0, 12, 20)

Code as follows:

xCount = 13
yCount = 21
numPages = 1
skip = [[4, 7], [4, 13], [8, 7], [8, 13]]

def createFrames():
  frameData = {'textFrameName': ''}
  framesData = [[[frameData for y in range(yCount)] for x in range(xCount)] for p in range(numPages)]

  for p in range(numPages):
    for x in range(xCount):
      for y in range(yCount):

        skipFrame = False

        for entry in skip:
          if entry[0] == x and entry[1] == y:
            skipFrame = True

        if skipFrame == False:
          framesData[p][x][y]['textFrameName'] = str(p) + ', ' + str(x) + ', ' + str(y)
          print(framesData[p][x][y]['textFrameName']) # shows p, x, y incrementing

  return framesData

def displayData(framesData):
  for p in range(numPages):
    for x in range(xCount):
      for y in range(yCount):

        skipFrame = False

        for entry in skip:
          if entry[0] == x and entry[1] == y:
            skipFrame = True

        if skipFrame == False:
          print(framesData[p][x][y]['textFrameName']) # shows 0, 12, 20 only


# Create text frames.
framesData = createFrames()

# Add data to dictionaries.
displayData(framesData)

Using python 2.7.16.

RBE
  • 175
  • 1
  • 11
  • 1
    `[frameData for y in ...]` is creating a bunch of references to a *single* `frameData` dict. Any changes made via any index of the list is equally visible via any other index. If you want all of the elements of the list to be independent dicts, you have to explicitly make copies: `[frameData.copy() for y in ...]` perhaps. – jasonharper Mar 20 '21 at 20:49
  • Where's the second function that simply displays the dict data? Please add it to your question. – martineau Mar 20 '21 at 20:50
  • 1
    The reason it "seems to work" in the first function is that you are `print`ing the current values as you update them, and thus not noticing that this also affects all the "previous" values, because they *are the same value*. Details are explained in the linked duplicate. – Karl Knechtel Mar 20 '21 at 20:50
  • 1
    @martineau AFAICT, the one that "simply displays the dict data" is the one, confusingly, named "addData". – Karl Knechtel Mar 20 '21 at 20:51
  • Corrected the misnamed function for clarity. Copying per @jasonharper resolved the issue. – RBE Mar 20 '21 at 23:19

1 Answers1

0

The problem comes from the list comprehension. You use frameData which is mutable, and so everytime you update it, you update it everywhere.

Below is a simplified process that shows the same behaviour:

>>> a = {"a" : 1}
>>> l = [a for _ in range(3)]
>>> l
[{'a': 1}, {'a': 1}, {'a': 1}]
>>> l[0]["a"] = 2
>>> l
[{'a': 2}, {'a': 2}, {'a': 2}] 

You can use .copy() to create a copy, or just create the object everytime in the comprehension

>>> a = {"a" : 1}
>>> l2 = [a.copy() for _ in range(3)]
>>> l2[0]["a"] = 2
>>> l2
[{'a': 2}, {'a': 1}, {'a': 1}]
>>> # or
>>> l3 = [{"a" : 1} for _ in range(3)]
>>> l3[0]["a"] = 2
>>> l3
[{'a': 2}, {'a': 1}, {'a': 1}]
FlorianGD
  • 2,336
  • 1
  • 15
  • 32