TypeError: unhashable type: 'list' (a few frames into program)
Question: So as my first project in python outside of Code Academy, I decided to make a
basic molecular dynamics simulator on pygame. It works fine for a while, but
as soon as electrons start to move too fast, and strip all the other electrons
off their atoms, I get the TypeError in the title. I have no idea where this
comes from, and only appears after the program has been running long enough
for me to mess up all the physics.

Now I know that the error is telling me that I'm trying to pass a list
somewhere I shouldn't, but I've looked over the program and can't figure out
where. The error pops up in the bit that tells electrons how to orbit their
atom `angle = findA(particles[el], particles[nuc]) + 0.001`, which is
controlled by the block of code near the end that tells the program in which
order to do physics, and the list of what each electron is meant to orbit is
controlled by another point, and so on.

So I decided just to give you all the code.

    
    
    import sys, pygame, math
    from pygame.locals import *
    
    pygame.init()
    sizeScreen = width, height = 1000, 700
    sizeMenu = width, height = 652, 700
    
    
    e = 1.6 * 10 ** -19
    particles = {}
    mx, my = 0, 0
    selected = []
    
    def findOrbital(el):
        for a in particles:
            if a != el and particles[a][4] != 'el':
                if findD(particles[el], particles[a]) < 5 * 10 ** -11 and PTI[particles[a][4]][7] > len(particles[a][5]):
                    particles[a][5].append(el)
                    particles[el][5].append(a)
    
    
    
    def searcher(List, item):
        for a in List:
            if a == item:
                return True
        return False
    
    def moveAtEls(el, nuc):
        angle = findA(particles[el], particles[nuc]) + 0.001
        particles[el][0] = particles[nuc][0] + 50 * math.cos(angle)
        particles[el][1] = particles[nuc][1] + 50 * math.sin(angle)
    
    
    def check(each):
        if particles[each][0] < 175:
            particles[each][2] = -particles[each][2]
            particles[each][0] = 175
        elif particles[each][0] > 1000:
            particles[each][2] = -particles[each][2]
            particles[each][0] = 1000
        if particles[each][1] < 0:
            particles[each][3] = -particles[each][3]
            particles[each][1] = 0
        elif particles[each][1] > 700:
            particles[each][3] = -particles[each][3]
            particles[each][1] = 700
        if particles[each][4] == 'el':
            a = 'n'
            findOrbital(each)
            if a != 'n':
                particles[each][5].append(a)
                particles[a][5].append(each)
    
    
    def findD(self, other):
        return math.hypot((self[0] - other[0]), (self[1] - other[1])) * 0.62 * 10 ** -12
    def findA(self, other):
        return math.atan2((self[1] - other[1]), (self[0] - other[0]))
    
    
    def move(self):
        for other in particles:
            if particles[other] != self and self[5] != particles[other] [5] and not searcher(self[5], other):
                D = findD(self, particles[other])
                if D == 0:
                    self[5].append(other)
                    particles[other][5].append(self)
                    break
                angle = findA(self, particles[other])
                F = 8987550000 * (PTI[self[4]][4] * PTI[particles[other][4]][4] * e ** 2)/D ** 2
                a = int(F/PTI[self[4]][5])
                ax = a * math.cos(angle)
                ay = a * math.sin(angle)
                self[2] += ax/(10 ** 16)
                self[3] += ay/(10 ** 16)
        self[0] += self[2]/(10 ** 8)
        self[1] += self[3]/(10 ** 8)
    
    
    pressed = ''
    press = {'Katom':[2,148,2,32,0],'Knuc':[2,148,36,66,0],'Kel':[2,148,70,100,0]}
    
    PTI = {'el':[0, 0, 0, 0, -1, 9.11 * 10 ** -31, pygame.image.load("electron.png"), 2],
    'HNuc' : [185, 214, 8, 37, 1, 1.7 * 10 ** -27, pygame.image.load("nuc/HNuc.png"), 2, 1],
    'HeNuc': [586, 613, 8, 37, 2, 6.6 * 10 ** -27, pygame.image.load("nuc/HeNuc.png"), 2, 2],
    'LiNuc': [185, 214, 40, 69, 1, 1.16 * 10 ** -26, pygame.image.load("nuc/LiNuc.png"), 8, 1],
    'BeNuc': [216, 246, 40, 69, 2, 1.53 * 10 ** -26, pygame.image.load("nuc/BeNuc.png"), 8, 2],
    'BNuc' : [428, 457, 40, 69, 3, 1.84 * 10 ** -26, pygame.image.load("nuc/BNuc.png"), 8, 3],
    'CNuc' : [460, 489, 40, 69, 4, 2.04 * 10 ** -26, pygame.image.load("nuc/CNuc.png"), 8, 4],
    'NNuc' : [492, 520, 40, 69, 5, 2.38 * 10 ** -26, pygame.image.load("nuc/NNuc.png"), 8, 5],
    'ONuc' : [523, 551, 40, 69, 6, 2.72 * 10 ** -26, pygame.image.load("nuc/ONuc.png"), 8, 6],
    'FNuc' : [554, 583, 40, 69, 7, 3.23 * 10 ** -26, pygame.image.load("nuc/FNuc.png"), 8, 7],
    'NeNuc': [586, 613, 40, 69, 8, 3.43 * 10 ** -26, pygame.image.load("nuc/NeNuc.png"), 8, 8]}
    
    menu = pygame.display.set_mode(sizeMenu)
    screenColor = pygame.Color(255, 255, 220)
    screen = pygame.display.set_mode(sizeScreen)
    edgeObj = pygame.image.load("edge.png")
    addEl = [pygame.image.load('addElectron1.png'), pygame.image.load('addElectron2.png')]
    addAtom = [pygame.image.load("addAtom1.png"), pygame.image.load("addAtom2.png"), pygame.image.load("atomTable.png")]
    addNucleus = [pygame.image.load("addNuc1.png"), pygame.image.load("addNuc2.png"), pygame.image.load("NucTable.png")]
    
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit(0)
    
            elif event.type == MOUSEMOTION:
                mx, my = event.pos
    
            mouseState = pygame.mouse.get_pressed()
            if mouseState[0]:
                for key in press:
                    if press[key][0] < mx <press[key][1] and press[key][2] < my < press[key][3]:
                        pressed = key
                        press[key][4] = 1
    
            if not mouseState[0] and pressed == 'Kel':
                particles[len(particles)] = [mx, my, 0, 0, 'el', []]
                pressed = ''
                press['Kel'][4] = 0
    
            if pressed != '':
                if not mouseState[0]:
                    if press[pressed][0] < mx <press[pressed][1] and press[pressed][2] < my < press[pressed][3]:
                        press[pressed][4] = 2
                        pressed = ''
    
            if press['Knuc'][4] == 2 or press['Katom'][4] == 2:
                if  mouseState[0]:
                    if 621 < mx < 651 and 2 < my < 14:
                        press['Knuc'][4] = 0
                        press['Katom'][4] = 0
                    if press['Knuc'][4] == 2:
                        for nuc in PTI:
                            if PTI[nuc][0] < mx < PTI[nuc][1] and PTI[nuc][2] < my < PTI[nuc][3]:
                                press['Knuc'][4] = 0
                                selected.append(nuc)
                    if press['Katom'][4] == 2:
                        for nuc in PTI:
                            if PTI[nuc][0] < mx < PTI[nuc][1] and PTI[nuc][2] < my < PTI[nuc][3]:
                                a = 0
                                selected.append(nuc)
                                while a < PTI[nuc][8]:
                                    selected.append('el')
                                    a += 1
                                press['Katom'][4] = 0
            if selected != []:
                if not mouseState[0]:
                    a = len(particles)
                    particles[a] = [mx, my, 0, 0, selected[0], [b for b in range(a+1, len(selected)-1)]]
                    for item in selected:
                        if item != selected[0]:
                            particles[len(particles)] = [mx, my, 0, 0, item, [a]]
                    selected = []
    
    
        for each in particles:
            check(each)
            move(particles[each])
            check(each)
            if len(particles[each][5]) > 0 and particles[each][4] == 'el':
                moveAtEls(each, particles[each][5][0])
            particles[each][5] = []
    
        screen.fill(screenColor)
        for a in particles:
            screen.blit(PTI[particles[a][4]][6], (particles[a][0] - 29, particles[a][1] - 31))
        menu.blit(edgeObj, (0, 0))
        menu.blit(addNucleus[press['Knuc'][4]], (2, 2))
        menu.blit(addAtom[press['Katom'][4]], (2, 2))
        menu.blit(addEl[press['Kel'][4]], (2, 2))
    
        pygame.display.flip()
    

Sorry if I'm being a nuisance by posting all the code, but I'm a complete n00b
and I'm surprised I got this far without help. I know the whole thing is
untidy, but if you could help with the error I'd greatly appreciate it.

Next time I'll just stick to `print("Hello, World!")`


Answer: So, what I believe is happening is that when execute the line `angle =
findA(particles[el], particles[nuc]) + 0.001`, either the `el` variable or the
`nuc` variable is a list of some sort, rather then a single object.

This throws an error because `particles` is a dict, and you cannot have a list
or any mutable type as a key in a dict.

So, given that this error does not execute immediately, I suspect that
somewhere along the line, in a piece of code that is not immediately executed,
you are accidentally passing in a `list` of some sort.

* * *

If you did mean to look things up in the `particles` dict by using a list as a
key, then you should convert the list to a `tuple` first: tuples are like
lists, but are immutable, and so can be hashed and used as a key of a dict.

