Al Avery Al Avery - 6 months ago 11
Python Question

Why does my function change its parameter outside of the function's scope?

My program uses pyplot to draw shapes. I have chosen to create the shapes in a very particular way because the program will eventually be solving this problem. Thus, the information for drawn shapes is contained in a data type I have named big_shape. A big_shape is a list of dicts, each dict containing the info needed to draw one 1x1 square, which I have named a unit_square.

In the step I am currently struggling with, I have created a function make_copies() that should do the following:

1. Iterate over each unit_square in a big_shape and run the transpose() function
-This should in turn create a new, transposed copy of the unit_square
2. Compile the new unit_squares into a second big_shape
3. Combine the new_big_shape with the original to make a third big_shape
4. Return the third big shape so that everything can be plotted.


The problem seems to come from the transpose() function. I expect the function to take an input of one unit square and output one copy of that unit square transposed to a new location, without affecting the original unit square. But, the transpose function seems to be affecting the input unit square in such a way that it ends up drawing both the original and the transposed over each other in the transposed spot.

Can you help me figure out why the transpose() function doesn't behave as expected?

import matplotlib.pyplot as plt


def make_unit_square(square_info):
'''
A unit square is a 1x1 square. The square_info parameter is a two item list containing coordinates of the square's left top corner
point (index [0], given as a list or tuple) and its color (index [1]). make_unit_square returns a dict that will eventually be
the information input into a plt.Polygon().
'''
points = [square_info[0],[square_info[0][0]+1,square_info[0][1]],[square_info[0][0]+1,square_info[0][1]-1],[square_info[0][0],square_info[0][1]-1]]
return {'type': 'unit square','points': points, 'color': square_info[1]}



def make_copies(big_shape):
'''
A big_shape is a list of unit_squares. Thus, any function taking a parameter big_shape will iterate over the
composite unit squares. The make_copies function should iterate over each unit_square in a big_shape and run the
transpose() function, which should in turn create a new, transposed copy of the unit_square. These new unit_squares
are compiled into a new big_shape, which is then combined with the old big_shape and returned by the make_copies
function so that the two can eventually be plotted at once.
'''
def transpose(unit_square,xdistance,ydistance):
new_unit_square = unit_square
new_points = [ [point[0] + xdistance, point[1] + ydistance] for point in unit_square['points']]
new_unit_square['points'] = new_points
return new_unit_square


#iterate over the big_shape, making a new, transposed copy of each of its composit unit_squares. THIS SHOULD LEAVE THE
#ORIGINAL BIG SHAPE UNCHANGED, BUT SOMETHING SEEMS TO GO WRONG.
new_big_shape = [transpose(x,0,10) for x in big_shape]
#combine the two big_shapes so they can be plotted at once
big_shape.extend(new_big_shape)

return big_shape


plt.axes()

#Below is the information for four unit_squares that will make up a big_shape
big_shape_1_info = [ [[0,1],'green'], [[0,2], 'green'], [[1,2],'green'], [[1,1],'pink'] ]
#Take that information and create a list of unit_squares, i.e. a big_shape.
big_shape_1 = [make_unit_square(x) for x in big_shape_1_info]
'''
Here we make an even larger big_shape by making a transposed copy of big_shape_1 and saving both the original
big_shape and its copy into one new big_shape. However, due to the bug, the unit_squares of big_shape_1 are
erroneously changed in this step, so what we get is not the original big_shape and a transposed copy, but rather
two transposed copies and no original.
'''
big_shape_2 = make_copies(big_shape_1)
'''
Next, we plot the new big_shape. This plotting is done by plotting each individual unit_square that makes up the big_shape.
'''
for unit_square in big_shape_2:
pol = plt.Polygon(unit_square['points'],color=unit_square['color'])
plt.gca().add_patch(pol)
print(unit_square)



plt.axis('scaled')
plt.show()

Answer

When you make what looks an assignment,

d = {1:[1,2,3]}
D = d

you are simply giving a new name to a dictionary object — the same reasoning applies to lists and other mutable objects — the fundamental is the object, and you can now use different names to refer to the same object.

No matter which name you use to modify an object, all these names reference the same object

d[2] = 3
D[3] = 4
print d, D # --> {1:[1,2,3], 2:3, 3:4}, {1:[1,2,3], 2:3, 3:4}

In your use case you want to make a copy of the dictionary...

Depending on the contents of your dictionary, you can use the dictionary's .copy() method or the copy.deepcopy(...) function from the copy module.

The method gives you a so called shallow copy, so the copy is a new dictionary, but if the items are mutable, when you modify an item also the item in the original dictionary is modified

d = {1:[1,2,3]}
D = d.copy()
D[2] = '2'
print d # --> {1:[1,2,3]} --- no new element in original
D[1][0] = 0
print d # --> {1:[0,2,3]} --- the original mutable content is modifed

if all the items in your dictionary are not mutable (e.g., string, numbers, tuples) then .copy() is OK.

On the other hand, if you have mutable items (e.g., lists, dictionaries, etc) and you want independent copies, you want copy.deepcopy()

from copy import deepcopy
d = {1:[1,2,3]}
D = deepcopy(d)
D[1][0]=0
print d # --> {1:[1,2,3]}

p.s. D = d.copy() is equivalent to creating a new dictionary, D = dict(d): also in this case what you have is a shallow copy.

Comments