matplotlib imshow, ArtistAnimation and class attribute
Question: I am trying to code a Conway's game of life in Python and to display the
evolution. I have trouble displaying the output. I have pasted my whole code
below.

I used this example as a base: [from the matplotlib
doc](http://matplotlib.org/examples/animation/dynamic_image2.html). My
animation is static, but if I use

    
    
    ims.append([plt.imshow(world.state+0, cmap=plt.cm.binary, interpolation='nearest')])
    

it animates correctly. I tried to write a function wolrd.get_state(), thinking
it was some kind of evaluation problem, to no avail.

This is driving me nuts, what am I missing here ?

Thanks

    
    
        import numpy as np
        from scipy.signal import convolve2d
    
        import matplotlib.pyplot as plt
        import matplotlib.animation as animation
    
    
        class World():
           """world information"""
    
           def __init__(self, grid):
               # noinspection PyNoneFunctionAssignment
               self.state = np.empty(grid.shape)
               self.__x_size = len(grid[0, :])
               self.__y_size = len(grid[:, 0])
               self.__x = range(self.__x_size)
               self.__y = range(self.__y_size)
               for i in self.__x:
                for j in self.__y:
                    self.state[i, j] = grid[i, j]
    
        def evolve(self):
            alive_neighbours = convolve2d(self.state, np.ones((3, 3)), mode='same', boundary='wrap') - self.state
            for i in self.__y:
                for j in self.__x:
                    if alive_neighbours[i, j] < 2:
                        self.state[i, j] = 0
                    elif alive_neighbours[i, j] == 3:
                        self.state[i, j] = 1
                    elif alive_neighbours[i, j] > 3:
                        self.state[i, j] = 0
            # self.state = np.random.randint(2, size=(self.__x_size, self.__y_size ))
    
    
    if __name__ == "__main__":
        nbx = 5
        nby = 5
        nb_gen = 5
    
        initial_seed = np.random.randint(2, size=(nbx, nby))
        world = World(initial_seed)
    
        ims = []
        fig = plt.figure()
    
        for i in range(nb_gen):
            ims.append([plt.imshow(world.state, cmap=plt.cm.binary, interpolation='nearest')])
            world.evolve()
    
        ani = animation.ArtistAnimation(fig, ims, interval=500, blit=True, repeat_delay=1000)
    
        plt.show()
    

Note: I have succesfully used:

    
    
    def animate(i):
        world.evolve()
        return (plt.imshow(world.state, cmap=plt.cm.binary, interpolation='nearest'),)
    
    ani = animation.FuncAnimation(fig, animate, frames=nb_gen, interval=500, blit=True, repeat_delay=1000)
    

but this is not what I want.


Answer: Each frame of the animation is displaying the same world state.

Since the world class only modifies the state, a reference to the _same_
object is being passed into imshow() for each frame. Each frame of the
animation is referencing the same array.

Matplotlib doesn't draw the animation to the screen until plt.show() is
called, so only the final version of the arrays passed into imshow() can be
seen, which is the final version of World.state.

This is the same reason that

    
    
    a = [1, 2, 3]
    b = a
    a.append(4)
    print(b)
    

outputs [1, 2, 3, 4]. b points to a, so when a changes, b changes.

Copying the image to be shown fixes the problem.

    
    
    ims.append([plt.imshow(world.state.copy(), cmap=plt.cm.binary, interpolation='nearest')])
    

This is also why world.state + 0 and np.random.randint(2, size=(self.__x_size,
self.__y_size )) worked: they both create new arrays, and don't modify the
arrays already passed into imshow().

