This is allowed because of the way attribute lookup works in Python and this is by design. In Python, many things that are discouraged, forbidden or impossible in other languages, are allowed to leverage your use case (if used wisely). Of course, more power implies more responsibility.
After all, we're all consenting adults here.
Some background information on attribute resolution
Class instances start with an empty __dict__
attribute where all object attributes are stored. By accessing x.someFunction
you are implicitly trying x.__dict__['someFunction']
. If 'someFunction'
does not exist in x.__dict__
, the same is tried for the class of x
, i.e. type(x).someFunction
or better type(x).__dict__['someFunction']
.
When your write x
by doing x.someFunction = 3
, what actually happens is x.__dict__['someFunction'] = 3
, regardless of what the reading attribute access might return.
The only real (?) magic happens during method calls, where self
is provided automatically, i.e. x.someFunction(4)
is resolved to type(x).__dict__['someFunction'](x, 4)
instead of type(x).__dict__['someFunction'](4)
or x.__dict__['someFunction'](4)
. This is somewhat related to attribute access and may cause confusion.
So, you actually do not "rebind" the function, but hide the class attribute someFunction
with the instance attribute someFunction
of x
. If you do
print(MyClass.someFunction)
print(MyClass().someFunction)
you will see that the method is still there. You can even restore the initial state with
del x.__dict__['someFunction']
Note: The things I described as resolution illustrate the concept. In reality the inner workings of python may be more subtle or complex, but they will have the same effect. For example, in Python 2, methods have been wrapped as so called unbound methods before being stored in the class dictionary. This has been dropped in Python 3, where the class dictionary contains the plain function.