The purpose of this is simplicity: There is no difference to accessing an instance inside or outside of a method. The object seen inside and outside of a method is completely equivalent, and by extension the same rules apply anywhere:
class MyClass:
def __init__(self):
print(self) # <__main__.MyClass object at 0x119636490>
self.b = 12 # assign to an instance
obj = MyClass()
print(obj) # <__main__.MyClass object at 0x119636490>
obj.b = 12 # # assign to an instance
Note that one can read and write attributes inside and outside methods, and these attributes are visible inside and outside of methods. In fact, Python has no concept of "inside" and "outside" of methods, aside from a few code-rewrites such as double-underscore name mangling.
This is both a result and the enabling feature to allow various inbuilt features of other languages to work without explicit support. For example, Python allows the equivalent of extension methods without extra syntax/functionality:
class MyPoint:
def __init__(self, x, y):
self.x, self.y = x, y
# Oops, forgot a repr!
def my_point_repr(self):
return f'{self.__class__.__name__}(x={self.x}, y={self.y})'
MyPoint.__repr__ = my_point_repr
print(MyPoint(1, 2)) # MyPoint(x=1, y=2)