First, note that Python's lack of private methods is deliberate. The language has a "we're all consenting adults here" attitude, so encapsulation is not enforced by the compiler. That's why internal methods and attributes are usually marked only with a single underscore.
Second, when you do
sbi.__balance = "abcdefg"
You're not setting the balance, but creating a new attribute. That's why the code doesn't crash. When you use the mangled name, then you're actually changing the value.
But you're changing it to a string, so it crashes when it tries to perform a numeric operation.
Here's an example showing how new attributes can be added to an existing object, and how mangled names can be changed only by referring to the mangled version.
class MyClass:
public = 'this is public'
_internal = 'this is internal'
__mangled = 'this is mangled'
def print_attributes(obj):
""" Prints all declared attributes in the given object. """
print([att for att in reversed(dir(obj)) if not att.endswith('__')])
instance = MyClass()
print_attributes(instance)
# ['_MyClass__mangled', '_internal', 'public']
# The class starts with those three attributes.
instance.public = 'new value'
print_attributes(instance)
# ['_MyClass__mangled', '_internal', 'public']
# We change the value of `public`.
instance._MyClass__mangled = 'new value'
print_attributes(instance)
# ['public', '_internal', '_MyClass__mangled']
# We change the value of the mangled attribute. Note the name used.
instance.__mangled = 'new value'
print_attributes(instance)
# ['public', '_internal', '__mangled', '_MyClass__mangled']
# If you try to change the mangled value by using `__mangled`, a new attribute is created instead.