From what little I know, + op for lists only requires the 2nd operand to be iterable, which \"ha\" clearly is.
In code:
>>> x = []
>>>          
        Using += with a list is like calling extend, not +.
extend with an iterable.+ with another list.I can only guess why this decision was made, but I imagine it is for performance reasons. Calling + results in a new object being created and all items being copied, whereas extend can use free space in the existing list object saving a copy in some cases. 
Another side-effect of this decision is that if you write x += y other references to the list will see the change but if you use x = x + y then they will not. This is demonstrated below:
>>> x = ['a','b'] >>> y = ['c', d'] >>> z = x >>> x += y >>> z ['a', 'b', 'c', 'd'] >>> x = ['a','b'] >>> y = ['c', d'] >>> z = x >>> x = x + y >>> z ['a', 'b']
References
Python source code for list.
Source code for +=:
static PyObject *
list_inplace_concat(PyListObject *self, PyObject *other)
{
    PyObject *result;
    result = listextend(self, other);
    if (result == NULL)
        return result;
    Py_DECREF(result);
    Py_INCREF(self);
    return (PyObject *)self;
}
Source code for +:
static PyObject *
list_concat(PyListObject *a, PyObject *bb)
{
    Py_ssize_t size;
    Py_ssize_t i;
    PyObject **src, **dest;
    PyListObject *np;
    if (!PyList_Check(bb)) {
        PyErr_Format(PyExc_TypeError,
                  "can only concatenate list (not \"%.200s\") to list",
                  bb->ob_type->tp_name);
        return NULL;
    }
    // etc ...
                                                                        When defining operators, there are two different "add" operators: One is called __add__, the other __iadd__. The latter one is for in-place additions with +=, the other one is the regular + operator. http://docs.python.org/reference/datamodel.html has more infos on that.
You're thinking about it backwards.  You're asking why x = x + 'ha' throws an exception, given that x += 'ha' works.  Really, the question is why x += 'ha' works at all.
Everyone agrees (I hope) that 'abc' + 'ha' and [1, 2, 3] + ['h', 'a'] should work.  And in these cases, overloading += to do in-place modification seems reasonable.
The language designers decided that [1, 2, 3] + 'ha' shouldn't, because you're mixing different types.  And that seems reasonable as well.
So the question is why they decided to allow mixing different types in the case of x += 'ha'.  In this case, I imagine there are a couple reasons:
x)In general, Python tries to let you do what you want, but where there's ambiguity, it tends to force you to be explicit.