mostly, it's because your algorithm can generate inputs which aren't constrained using the algorithm you chose.
first, random.randint(1,100)
will produce a number between 1 and 100. you are really using 100 - randint(1,100)
though, which will occasionally produce 0s. if you get two items with move=0, neither one can actually move to engage the other one, so your loop will never exit. maybe just use self.move = random.randint(1,100)
instead etc. (same is true for life and other things).
there are other constraints which are also invalid - take these lines:
self.life = (self.life + self.defense) - enemy.attack
enemy.life = (enemy.life + enemy.defense) - self.attack
there are two issues with this. one, if x.defense > y.attack, you are actually adding life to the object. you probably want to saturate this at the initial value of self.life (or 100 if you really want healing).
second, even if you do that, you can have a case like this: self.attack = 20 self.defense = 30 enemy.attack = 20 enemy.defense = 30
which is basically a pillow fight :) since attack is always less than defense, neither life is actually ever going to go down, and this loop will run forever. you might want to introduce a random element here.