These two cases are more different than they seem, and it isn't because of the types involved.
x = 3
This changes what value x is looking at. Reassigning x using = does not change the 3. It wouldn't matter if the right hand side were a int, or a list. Whatever is on the right is put into the variable on the left.
x[0] = 'd'
This looks the same, but it's actually quite different. Instead of a simple reassignment, this is actually internally translated to
x.__setitem__(0, 'd')
Instead of being just a reassignment, this is actually a mutative operation that changes the object x is holding.
So the reason the latter acts differently is that instead of changing what object x is holding, you're altering the object itself that x is holding.
You can tell the two cases apart based on what's on the left of =. If the left is a simple variable name (x =), it's a plain reassignment. If the left side uses [] (x[0] =) or anything other kind of "accessor" syntax like a . (x.attr =), you're altering the object that x is holding.